利用Three.js實現互動排斥效果示例

文將演示如何使用three。js和TweenMax(GSAP)為元素網格構建有趣的排斥效果。效果重現BestServedBold的Dribbble拍攝,在文章的最後還給出了演示全程。

注意:我們假設您已經擁有一些基本的JavaScript和three。js知識。如果您不熟悉它,我強烈建議您檢視官方和。

效果圖如下:

利用Three.js實現互動排斥效果示例

原創的想法

最初的想法是基於BestServedBold的Dribbble拍攝全息相互作用:

影片載入中。。。

核心概念

我們的想法是建立一個隨機元素網格,對滑鼠移動做出反應。

網格的每個元素將根據從當前滑鼠位置到元素中心的距離更新其Y位置,旋轉和縮放值。

利用Three.js實現互動排斥效果示例

滑鼠離元素越近,它就越大。

利用Three.js實現互動排斥效果示例

我們還為此定義了一個半徑,僅影響該半徑內的一個元素或任意數量的元素。半徑越大,移動滑鼠時元素的反應就越多。

利用Three.js實現互動排斥效果示例

開始

首先,我們必須為演示設定HTML頁面。這是一個簡單的樣板檔案,因為所有程式碼都將在canvas元素中執行:

Repulsive Force Interavtion 如您所見,我們還從CDN 連結到和TweenMax。Helper函式讓我們定義一些輔助函式來計算兩點之間的距離,對映值並將度轉換為弧度:const radians = (degrees) => { return degrees * Math。PI / 180;}const distance = (x1, y1, x2, y2) => { return Math。sqrt(Math。pow((x1 - x2), 2) + Math。pow((y1 - y2), 2));}const map = (value, start1, stop1, start2, stop2) => { return (value - start1) / (stop1 - start1) * (stop2 - start2) + start2}網格元素(Grid Elements)在構建網格之前,我們需要定義將要使用的物件:1。 框(Box) // credits for the RoundedBox mesh - import RoundedBoxGeometry from ‘roundedBox’; class Box { constructor() { this。geom = new (。5, 。5, 。5, 。02, 。2); this。rotationX = 0; this。rotationY = 0; this。rotationZ = 0; } }2。 錐體(Cone) class Cone { constructor() { this。geom = new (。3, 。5, 32); this。rotationX = 0; this。rotationY = 0; this。rotationZ = radians(-180); } }3。 花托(Torus) class Torus { constructor() { this。geom = new (。3, 。12, 30, 200); this。rotationX = radians(90); this。rotationY = 0; this。rotationZ = 0; } }建立3D世界在我們的主類中,我們為設定建立了一個函式: setup() { // handles mouse coordinates mapping from 2D canvas to 3D world this。raycaster = new (); this。gutter = { size: 1 }; this。meshes = []; this。grid = { cols: 14, rows: 6 }; this。width = window。innerWidth; this。height = window。innerHeight; this。mouse3D = new THREE。Vector2(); this。geometries = [ new Box(), new Tourus(), new Cone() ]; window。addEventListener(‘mousemove’, this。onMouseMove。bind(this), { passive: true }); // we call this to simulate the initial position of the mouse cursor this。onMouseMove({ clientX: 0, clientY: 0 }); }滑鼠移動處理程式 onMouseMove({ clientX, clientY }) { this。mouse3D。x = (clientX / this。width) * 2 - 1; this。mouse3D。y = -(clientY / this。height) * 2 + 1; }建立我們的3D場景 createScene() { this。scene = new THREE。Scene(); this。renderer = new THREE。WebGLRenderer({ antialias: true, alpha: true }); this。renderer。setSize(window。innerWidth, window。innerHeight); this。renderer。setPixelRatio(window。devicePixelRatio); this。renderer。shadowMap。enabled = true; this。renderer。shadowMap。type = THREE。PCFSoftShadowMap; document。body。appendChild(this。renderer。domElement); }相機現在讓我們為我們的場景新增一個攝像頭: createCamera() { this。camera = new (20, window。innerWidth / window。innerHeight, 1); // set the distance our camera will have from the grid this。camera。position。set(0, 65, 0); // we rotate our camera so we can get a view from the top this。camera。rotation。x = -1。57; this。scene。add(this。camera); }隨機物件helper我們想隨機放置各種盒子,錐體和tourus物件,所以我們建立了一個helper: getRandomGeometry() { return this。geometries[Math。floor(Math。random() * Math。floor(this。geometries。length))]; }建立Mesh處理器這只是基於幾何和材質建立網格的一個小幫手 getMesh(geometry, material) { const mesh = new THREE。Mesh(geometry, material); mesh。castShadow = true; mesh。receiveShadow = true; return mesh; }網格現在我們將把這些隨機元素放在網格佈局中

利用Three.js實現互動排斥效果示例

createGrid() { // create a basic 3D object to be used as a container for our grid elements so we can move all of them together this。groupMesh = new THREE。Object3D(); const meshParams = { color: ‘#ff00ff’, metalness: 。58, emissive: ‘#000000’, roughness: 。18, }; // we create our material outside the loop to keep it more performant const material = new (meshParams); for (let row = 0; row < this。grid。rows; row++) { this。meshes[row] = []; for (let col = 0; col < this。grid。cols; col++) { const geometry = this。getRandomGeometry(); const mesh = this。getMesh(geometry。geom, material); mesh。position。set(col + (col * this。gutter。size), 0, row + (row * this。gutter。size)); mesh。rotation。x = geometry。rotationX; mesh。rotation。y = geometry。rotationY; mesh。rotation。z = geometry。rotationZ; // store the initial rotation values of each element so we can animate back mesh。initialRotation = { x: mesh。rotation。x, y: mesh。rotation。y, z: mesh。rotation。z, }; this。groupMesh。add(mesh); // store the element inside our array so we can get back when need to animate this。meshes[row][col] = mesh; } } //center on the X and Z our group mesh containing all the grid elements const centerX = ((this。grid。cols - 1) + ((this。grid。cols - 1) * this。gutter。size)) * 。5; const centerZ = ((this。grid。rows - 1) + ((this。grid。rows - 1) * this。gutter。size)) * 。5; this。groupMesh。position。set(-centerX, 0, -centerZ); this。scene。add(this。groupMesh); }環境光接下來,我們將新增一個環境光,以提供一些不錯的色彩效果:

利用Three.js實現互動排斥效果示例

addAmbientLight() { const light = new (‘#2900af’, 1); this。scene。add(light); }聚光燈我們還將SpotLight新增到場景中以獲得逼真的觸感:

利用Three.js實現互動排斥效果示例

RectArea光為了照亮一些均勻的光,我們使用RectArea光:

利用Three.js實現互動排斥效果示例

addRectLight() { const light = new (‘#0077ff’, 1, 2000, 2000); light。position。set(5, 50, 50); light。lookAt(0, 0, 0); this。scene。add(light); }點光源對於最終的燈光效果,我們建立了一個PointLight函式來新增我們想要的光線:

利用Three.js實現互動排斥效果示例

addPointLight(color, position) { const light = new (color, 1, 1000, 1); light。position。set(position。x, position。y, position。z); this。scene。add(light); }陰影層(Shadow Floor)現在,我們需要新增一個形狀,作為滑鼠游標能夠懸停的對映物件: addFloor() { const geometry = new (100, 100); const material = new ({ opacity: 。3 }); this。floor = new (geometry, material); this。floor。position。y = 0; this。floor。receiveShadow = true; this。floor。rotateX(- Math。PI / 2); this。scene。add(this。floor); }繪製/動畫元素這是處理所有動畫的功能; 它將在requestAnimationFrame回撥內的每一幀上呼叫: draw() { // maps our mouse coordinates from the camera perspective this。raycaster。setFromCamera(this。mouse3D, this。camera); // checks if our mouse coordinates intersect with our floor shape const intersects = this。raycaster。intersectObjects([this。floor]); if (intersects。length) { // get the x and z positions of the intersection const { x, z } = intersects[0]。point; for (let row = 0; row < this。grid。rows; row++) { for (let col = 0; col < this。grid。cols; col++) { // extract out mesh base on the grid location const mesh = this。meshes[row][col]; // calculate the distance from the intersection down to the grid element const mouseDistance = distance(x, z, mesh。position。x + this。groupMesh。position。x, mesh。position。z + this。groupMesh。position。z); // based on the distance we map the value to our min max Y position // it works similar to a radius range const maxPositionY = 10; const minPositionY = 0; const startDistance = 6; const endDistance = 0; const y = map(mouseDistance, startDistance, endDistance, minPositionY, maxPositionY); // based on the y position we animate the mesh。position。y // we don´t go below position y of 1 TweenMax。to(mesh。position, 。4, { y: y < 1 ? 1 : y }); // create a scale factor based on the mesh。position。y const scaleFactor = mesh。position。y / 2。5; // to keep our scale to a minimum size of 1 we check if the scaleFactor is below 1 const scale = scaleFactor < 1 ? 1 : scaleFactor; // animates the mesh scale properties TweenMax。to(mesh。scale, 。4, { ease: Back。easeOut。config(1。7), x: scale, y: scale, z: scale, }); // rotate our element TweenMax。to(mesh。rotation, 。7, { ease: Back。easeOut。config(1。7), x: map(mesh。position。y, -1, 1, radians(45), mesh。initialRotation。x), z: map(mesh。position。y, -1, 1, radians(-90), mesh。initialRotation。z), y: map(mesh。position。y, -1, 1, radians(90), mesh。initialRotation。y), }); } } } }這就是它!這裡有更多的可能性,即新增更多物件等。看看以下網格變化:

利用Three.js實現互動排斥效果示例

或相機旋轉變數:

利用Three.js實現互動排斥效果示例

最後,提供本文的Demo的訪問網址:http://www。ikinsoft。com/demo/interactive/index。html