パーティクルを作成する(HTML/JS)

前書き

パーティクルを作成する方法を掲載しています。

目次

  1. 雪を降らせる
  2. 色々な形に整列させる
  3. 参考

雪を降らせる

three.jsを使って作成しています。

概要

  • 雪の画像を貼り付けて、ランダムに配置
  • x,y,z方向に移動させる

見本


<div class="wrap js-wrap-a">
  <canvas class="canvas js-canvas"></canvas>
</div>

.wrap{
  position: relative; padding: 50% 0 0; overflow: hidden;
}
.canvas{
  position: absolute; top: 0; left: 0; width: 100%; height: 100%;
}

import * as THREE from './three_r127/build/three.module.js';
import { OrbitControls } from './three_r127/examples/jsm/controls/OrbitControls.js';

// サイズ------------------
const wrap = document.querySelector(".js-wrap-a");
let wrapWidth = wrap.clientWidth;
let wrapHeight = wrap.clientHeight;

// レンダラー------------------
const renderer = new THREE.WebGLRenderer({
  canvas: document.querySelector(".js-canvas"),
  antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(wrapWidth, wrapHeight);

// シーン------------------
const scene = new THREE.Scene();
scene.background = new THREE.Color( 0x000000 ); //背景色

// カメラ------------------
const camera = new THREE.PerspectiveCamera( 60, wrapWidth / wrapHeight );
camera.position.set( 0, 0, 100 );

// カメラコントローラーを作成
const controls = new OrbitControls( camera, renderer.domElement );

// 物体 ------------------
// テクスチャ
const texture = new THREE.TextureLoader().load( './snow.svg' );
const material = new THREE.SpriteMaterial({
  map: texture,
  color: 0xffffff,
});
// グループ
const group = new THREE.Group();
scene.add( group );
// パーティクル
const num = 1000; // パーティクルの数
const range = 1000; // 配置する範囲
const rangeHalf = range / 2;

for ( let i = 0; i < num; i ++ ) {

  const sprite = new THREE.Sprite( material ); //常に正面を向く3Dオブジェクト
  sprite.position.x = range * (Math.random() - 0.5);
  sprite.position.y = range * (Math.random() - 0.5);
  sprite.position.z = range * (Math.random() - 0.5);
  sprite.scale.x = sprite.scale.y = sprite.scale.z = Math.random() * 10 + 5;
  sprite.matrixAutoUpdate = false;
  sprite.updateMatrix();
  group.add( sprite );

}

//位置を変える------------------
const windX = 0.25; //x方向の速度(共通)
const variationX = 0.25; //x方向の速度にバラつきを加える
const gravityY = 0.5 //y方向の速度(共通)
const variationY = 0.25;//y方向の速度にバラつきを加える
const windZ = 0; //z方向の速度(共通)
const variationZ = 0.5;//z方向の速度にバラつきを加える

const positionUpdate = () =>{

  for ( let i = 0; i < num; i ++ ) {

    const obj = group.children[ i ];
    //x
    if( obj.position.x < - rangeHalf){
      obj.position.x = rangeHalf;
    }else if ( obj.position.x > rangeHalf){
      obj.position.x = - rangeHalf;
    }else{
      obj.position.x +=  windX + variationX * ( i / num - 0.5  ) ;
    }
    //y
    if( obj.position.y < - rangeHalf){
      obj.position.y = rangeHalf;
    }else{
      obj.position.y -= gravityY + variationY * i / num ;
    }
    //z
    if( obj.position.z < - rangeHalf){
      obj.position.z = rangeHalf;
    }else if ( obj.position.z > rangeHalf){
      obj.position.z = - rangeHalf;
    }else{
      obj.position.z +=  windZ + variationZ * ( i / num - 0.5  ) ;
    }
    obj.matrixAutoUpdate = false;
    obj.updateMatrix();
  }

}

//リサイズ------------------
const wrapResize = () =>{
  // サイズ
  wrapWidth = wrap.clientWidth;
  wrapHeight = wrap.clientHeight;
  // レンダラー
  renderer.setSize(wrapWidth, wrapHeight);
}

//一定時間毎に処理------------------
let tick;
const switching = (e) => {
  if( e[0].isIntersecting ){ //見えてる時
    tick = () => {
      wrapResize(); //リサイズ
      positionUpdate(); //位置を変える
      renderer.render(scene, camera); //レンダリング
      requestAnimationFrame( tick ); //繰り返し
    }
    requestAnimationFrame( tick );
  }else{ //見えてない時
    tick = () => {
      cancelAnimationFrame( tick )
    }
  }
}

//見えているかどうか(Intersection Observer API)------------------

//要素の監視
const createObserver = () => {
  let observer;
  const options = { root: null, rootMargin: "0%", threshold: 0 };
  observer = new IntersectionObserver(switching, options); //コールバック関数とオプションを渡す
  observer.observe(wrap); //要素の監視を開始
}

//実行
createObserver();

色々な形に整列させる

three.jsを使って作成しています。

概要

  • 形の座標を準備
  • 適用する座標を時間毎に切り替える

見本


<div class="wrap js-wrap-b">
  <canvas class="canvas js-canvas02"></canvas>
</div>

.wrap{
  position: relative; padding: 50% 0 0; overflow: hidden;
}
.canvas{
  position: absolute; top: 0; left: 0; width: 100%; height: 100%;
}

import * as THREE from './three_r127/build/three.module.js';
import { OrbitControls } from './three_r127/examples/jsm/controls/OrbitControls.js';
import { gsap } from './gsap_3.6.1/esm/gsap-core.js';

const wrap = document.querySelector(".js-wrap-b");
let wrapWidth = wrap.clientWidth;
let wrapHeight = wrap.clientHeight;

// レンダラー------------------
const renderer = new THREE.WebGLRenderer({
  canvas: document.querySelector(".js-canvas02"),
  antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(wrapWidth, wrapHeight);

// シーン------------------
const scene = new THREE.Scene();
scene.background = new THREE.Color( 0xf5edf3 ); //背景色

// カメラ------------------
const camera = new THREE.PerspectiveCamera( 45, wrapWidth / wrapHeight );
camera.position.set( 250, 250, 250 );

// カメラコントローラーを作成
const controls = new OrbitControls( camera, renderer.domElement );
controls.autoRotate = true; // 自動回転をONにする
controls.autoRotateSpeed = 0.25; // 自動回転の速度
controls.enableDamping = true; // 視点操作のイージングをONにする
controls.dampingFactor = 0.2; // 視点操作のイージングの値
controls.rotateSpeed = 0.5; // 視点変更の速さ

// 光源------------------
// 環境光源
const aLight = new THREE.AmbientLight(0xffffff, 0.75);
scene.add(aLight);
// 平行光源
const dLight = new THREE.DirectionalLight(0xffffff, 0.5);
scene.add(dLight);

// 物体 ------------------
// グループ
const group = new THREE.Group();
scene.add( group );

const geometryRadius = 10
const geometry = new THREE.IcosahedronGeometry( geometryRadius, 15 );
const baseNum = 7;
const num = baseNum**3; // パーティクルの数
const baseRange = 150;


// 位置、色 ------------------
const colors = [];
const positions = {  rd: [], sp: [], cb: [], hx: [] };

//色
for ( let i = 0; i <= num; i ++ ) {
  const spRate = ( num - i ) / num;
  colors.push(
    Math.round( spRate * 90 + 305 ) / 360, // 色相
    Math.round( spRate * 20 + 40 ) / 100, // 彩度
    Math.round( spRate * 20 + 40 ) / 100, // 輝度
  );
}

//ラムダムな位置
const rdRange = baseRange * 4 ;
for ( let i = 0; i < num; i ++ ) {
  positions.rd.push(
    rdRange * (Math.random() - 0.5),
    rdRange * (Math.random() - 0.5),
    rdRange * (Math.random() - 0.5)
  );
}

//球の位置
//各層にパーティクルをいくつずつ分配するか
const spLayers = 3; // 何層にするか
const spNums = [];
const spNumsCalc = () =>{
  //配分の調整
  const spRate = 2;
  //分母
  let spTotal = 0;
  for ( let i = 1; i <= spLayers; i ++ ) {
    spTotal += i**spRate; //べき乗
  }
  //各層に配置するパーティクルの個数
  for ( let i = 1; i <= spLayers; i ++ ) {
    const spNum = Math.round ( num * i**spRate / spTotal ); //個数 * 分子/分母
    spNums.push( spNum );
  }
}
spNumsCalc();

//各層毎に処理
for ( let i = 1; i <= spLayers; i ++ ) {

  const eachNum = spNums[ i - 1 ]; //パーティクルの個数
  const eachRange = baseRange / spLayers * i; //中心からの距離

  for ( let j = 0; j < eachNum; j ++ ) {
    const phi = Math.acos( j / eachNum * 2 - 1 ); // アークコサイン(-1~1)→ ラジアン(0~180 * Math.PI/180)
    const theta = Math.sqrt( eachNum * Math.PI ) * phi;
    positions.sp.push(
    	eachRange * Math.cos( theta ) * Math.sin( phi ),
    	eachRange * Math.sin( theta ) * Math.sin( phi ),
    	eachRange * Math.cos( phi )
    );
  }
}

//立方体の位置
const cbRange = baseRange * 0.25;
for ( let x = 0; x < baseNum; x ++ ) {
  for ( let y = 0; y < baseNum; y ++ ) {
    for ( let z = 0; z < baseNum; z ++ ) {
      positions.cb.push(
        cbRange * ( x - baseNum / 2 ),
        cbRange * ( y - baseNum / 2 ),
        cbRange * ( z - baseNum / 2 )
      );
    }
  }
}

//螺旋の位置
const hxNum = 45; // 一周あたりの個数
const hxRange = baseRange * 1; // 螺旋の半径
const hxRize = geometryRadius / hxNum * 2.5; // y方向にずらす距離
for ( let i = 0; i < num; i ++ ) {
  const phi = 360 / hxNum * i * Math.PI / 180;
  positions.hx.push(
    hxRange * Math.sin( phi ),
    hxRize * ( i - num / 2 ),
    hxRange * Math.cos( phi )
  );
}

// パーティクル
let position = positions.rd;
for ( let i = 0; i < num; i ++ ) {

  const sphere = new THREE.Mesh( geometry, new THREE.MeshToonMaterial() );
  sphere.position.set(
    position[ 3 * i ],
    position[ 3 * i + 1 ],
    position[ 3 * i + 2 ]
  );
  sphere.material.color.setHSL(
    colors[ 3 * i ],
    colors[ 3 * i + 1 ],
    colors[ 3 * i + 2 ]
  );

  group.add( sphere );
}

//位置を変える------------------
let count = 0;
const positionUpdate = () =>{

  let update = false;

  if(count === 300 ){
    position = positions.sp; //球
    update = true;
  } else if( count === 600 ){
    position = positions.hx; //螺旋
    update = true;
  } else if( count === 900 ){
    position = positions.cb; // 立方体
    update = true;
  } else if( count === 1200 ){
    position = positions.rd; // ランダム
    update = true;
    count = 0;
  }

  if( update ){
    for ( let i = 0; i < num; i ++ ) {
      const sphere = group.children[ i ];
      gsap.to(sphere.position, {
        x:position[ 3 * i ],
        y:position[ 3 * i + 1 ],
        z:position[ 3 * i + 2 ],
        duration: 1}
      );
    }
    update = false;
  }
  count++;
}

//リサイズ------------------
const wrapResize = () =>{
  // サイズ
  wrapWidth = wrap.clientWidth;
  wrapHeight = wrap.clientHeight;
  // レンダラー
  renderer.setSize(wrapWidth, wrapHeight);
}

//一定時間毎に処理------------------
let tick;
const switching = (e) => {
  if( e[0].isIntersecting ){ //見えてる時
    tick = () => {
      wrapResize(); //リサイズ
      positionUpdate(); //位置を更新
      controls.update(); //カメラコントローラー
      renderer.render(scene, camera); //レンダリング
      requestAnimationFrame( tick ); //繰り返し
    }
    requestAnimationFrame( tick );
  }else{ //見えてない時
    tick = () => {
      cancelAnimationFrame( tick )
    }
  }
}

//見えているかどうか(Intersection Observer API)------------------

//要素の監視
const createObserver = () => {
  let observer;
  const options = { root: null, rootMargin: "0%", threshold: 0 };
  observer = new IntersectionObserver(switching, options); //コールバック関数とオプションを渡す
  observer.observe(wrap); //要素の監視を開始
}

//実行
createObserver();

参考

Three.jsのスプライト

threejs examples #css3d_sprites

Three.jsで螺旋アニメーション

関連記事

文字を立体で表示する(HTML/CSS/JS)

360度画像を表示する(HTML/JS)

うにょうにょ動かす(HTML/CSS/JS)

円を描くように要素を動かす(HTML/CSS/JS)

マウスの位置に合わせて要素を動かす(HTML/CSS/JS)

slick(スライダー)に関する小技(HTML/CSS/JS)

先頭に戻る
ページの先頭に戻る