パーティクルを作成する(HTML/JS)
前書き
パーティクルを作成する方法を掲載しています。
目次
- 下から飛び出す
- キラキラさせる
- 雪を降らせる
- 色々な形に整列させる
- 参考
下から飛び出す
Web Animations APIを使って動かしてみました。
概要
- 横方向は一定の速度で動かす
- 縦方向はイージングを調整して、放物線を描くような動きに見せる
- 再生時間を縦方向の位置に合わせて調整する
- 回転させる
見本
<div class="wrap js-wrap-popout">
<div class="item js-item-popout">
<div class="child">
<div class="gchild"></div>
</div>
</div>
</div>
.wrap{
position: relative; background: #f5edf3; padding: 50% 0 0; overflow: hidden;
}
.item{
position: absolute; top: 0; left: 0;
}
.child{
}
.gchild{
padding: 100% 0 0; border-radius: 50%;
}
const num = 50;//パーティクルの数
const wrap = document.querySelector(".js-wrap-popout");
const wrapHeight = wrap.offsetHeight;
//乱数を返す関数
const getRandom = ( min, max ) => { //min以上max未満
return Math.floor( Math.random() * ( max - min ) + min );
}
//要素を複製
const target = document.querySelector(".js-item-popout");
for (let i = 0; i < num; i++){
const targetClone = target.cloneNode(true);
wrap.appendChild( targetClone );
}
//要素毎にアニメーションを設定する------------------
const arrFunc = [];
const colors = ['#e94d15', '#f18d1d', '#f8b633', '#a74535'];
const baseColor = 'transparent';
document.querySelectorAll(".js-item-popout").forEach((item , i) => {
//要素
const itemWidth = getRandom( 4, 8 ); //横幅
item.style.width = itemWidth + '%';
const itemHeight = item.offsetHeight; //高さ
//子要素
const child = item.firstElementChild;
//孫要素
const gChild = child.firstElementChild;
gChild.style.backgroundImage = 'conic-gradient(from 112.5deg,' + colors[ i % 4 ] + ' 0%, ' + colors[ i % 4 ] + ' 85%, ' + baseColor + ' 85%, ' + baseColor + ' 100%)';
//横
const xRatio = 100 / itemWidth; //表示場所の幅 ÷ 要素の幅(パーセント)
const xBegin = ( xRatio - 1 ) * 50; //中央
const xRandom = getRandom( 0, xRatio - 1 ); //表示場所に収まる範囲で乱数を生成
const xEnd = xRandom * 100;
//縦
const yRatio = wrapHeight / itemHeight; //表示場所の高さ ÷ 要素の高さ
const yBottom = yRatio * 100; //一番下
const yRandom = getRandom( 1, yRatio - 1 ); //表示場所に収まる範囲で乱数を生成
const yTop = yRandom * 100; //値が大きいほど、低い位置
//回転
const rBegin = 0;
const rEnd = - ( xBegin - xEnd ) * 1; //横の移動方向と回転方向を合わせる
//時間
const durationTime = ( 1 - ( yTop / yBottom ) ** 2) * 3000; //縦の位置と合わせる。yTopが大きい(位置が低い)ほど、短い時間
const delayTime = - getRandom( 0, 3000 );
//アニメーション(横)
const func01 = item.animate(
[
{ transform: 'translateX(' + xBegin + '%)'},
{ transform: 'translateX(' + xEnd + '%)'},
], {
duration: durationTime, //ミリ秒
iterations: Infinity, //繰り返し回数
// iterationStart: getRandom( 0, 10 ) / 10 // ← safari 未対応
delay: delayTime ,//iterationStartの代用
}
);
//アニメーション(縦)
const func02 = child.animate(
[
{ transform: 'translateY(' + yBottom + '%)', easing: 'cubic-bezier(0.33, 1, 0.68, 1)' },
{ transform: 'translateY(' + yTop + '%)', easing: 'cubic-bezier(0.32, 0, 0.67, 0)' },
{ transform: 'translateY(' + yBottom + '%)'},
], {
duration: durationTime, //ミリ秒
iterations: Infinity, //繰り返し回数
delay: delayTime,
}
);
//アニメーション(回転)
const func03 = gChild.animate(
[
{ transform: 'rotate(' + rBegin + 'deg)'},
{ transform: 'rotate(' + rEnd + 'deg)'},
], {
duration: durationTime, //ミリ秒
iterations: Infinity, //繰り返し回数
delay: delayTime ,
}
);
arrFunc.push( func01, func02, func03);
});
//見えている時だけ動かす------------------
const switching = (e) => {
if( e[0].isIntersecting ){ //見えてる時
arrFunc.forEach((func) => {
func.play();
});
}else{ //見えてない時
arrFunc.forEach((func) => {
func.pause();
});
}
}
//見えているかどうか(Intersection Observer API)------------------
const createObserver = () => {
let observer;
const options = { root: null, rootMargin: "0%", threshold: 0 };
observer = new IntersectionObserver(switching, options); //コールバック関数とオプションを渡す
observer.observe(wrap); //要素の監視を開始
}
createObserver();
キラキラさせる
Web Animations APIを使って動かしてみました。
概要
- 横方向は無作為に配置する
- 縦方向に移動させるアニメーションの開始点を無作為に設定する
- 透明度を変化させる
- 色相を変化させる
見本
<div class="wrap js-wrap-kirakira">
<div class="item js-item-kirakira">
<svg viewBox="0 0 82.17 82.18">
<path d="M72.12,10.06c-29.72,26.95-32,27-61.67,0,26.92,29.75,27,31.94,0,61.66,29.72-26.95,32-26.95,61.66,0C45.14,42,45.17,39.77,72.12,10.06Z"/>
<path d="M1.18,77.71c-1.45,1.46-1.47,2.9-.54,3.83S3,82.47,4.46,81,6.4,75.78,6.4,75.78,2.64,76.26,1.18,77.71Z"/>
<path d="M81,4.46C82.47,3,82.44,1.54,81.53.63s-2.34-.92-3.82.55-1.94,5.21-1.94,5.21S79.54,5.92,81,4.46Z"/>
<path d="M81,77.71c-1.46-1.45-5.22-1.93-5.22-1.93s.48,3.76,1.94,5.21,2.89,1.48,3.82.55S82.44,79.17,81,77.71Z"/>
<path d="M.63.64c-.9.9-.93,2.35.55,3.82S6.39,6.4,6.39,6.4,5.91,2.64,4.46,1.18,1.54-.27.63.64Z"/>
</svg>
</div>
</div>
.wrap{
position: relative; background: #000; padding: 50% 0 0; overflow: hidden;
}
.item{
position: absolute; top: 0;
}
const num = 50;//パーティクルの数
const wrap = document.querySelector(".js-wrap-kirakira");
const wrapHeight = wrap.offsetHeight;
//乱数を返す関数
const getRandom = ( min, max ) => { //min以上max未満
return Math.floor( Math.random() * ( max - min ) + min );
}
//要素を複製
const target = document.querySelector(".js-item-kirakira");
for (let i = 0; i < num; i++){
const targetClone = target.cloneNode(true);
wrap.appendChild( targetClone );
}
//要素毎にアニメーションを設定する------------------
const arrFunc = [];
document.querySelectorAll(".js-item-kirakira").forEach((item) => {
item.style.left = getRandom( 0, 94 ) + "%"; //左端からの距離
item.style.width = getRandom( 4, 8 ) + '%'; //横幅
//アニメーション(移動)
const func01 = item.animate(
[
{ transform: 'translateY(' + wrapHeight / item.offsetHeight * 100 + '%)'},
{ transform: 'translateY(-100%)'},
], {
duration: getRandom( 20000, 40000 ), //ミリ秒
iterations: Infinity, //繰り返し回数
// iterationStart: getRandom( 0, 10 ) / 10 // ← safari 未対応
delay: - getRandom( 20000, 40000 ) //iterationStartの代用
}
);
//アニメーション(透明度)
const func02 = item.animate(
[
{ opacity: 0 },
{ opacity: 1 },
{ opacity: 0 },
], {
duration: getRandom( 5000, 7500 ), //ミリ秒
iterations: Infinity, //繰り返し回数
delay: - getRandom( 5000, 7500 ),
}
);
//アニメーション(色)
const svg = item.firstElementChild;
const h1 = getRandom( 0, 180 ); //色相1
const h2 = getRandom( 180, 360 ); //色相2
const s = 100; //彩度
const l = getRandom( 60, 80 ); //輝度
const hsl1 = "hsl(" + h1 + "deg," + s + "%," + l + "%)";
const hsl2 = "hsl(" + h2 + "deg," + s + "%," + l + "%)";
const shadow1 = "drop-shadow(0px 0px 5px " + hsl1 + ")";
const shadow2 = "drop-shadow(0px 0px 5px " + hsl2 + ")";
const func03 = svg.animate(
[
{ fill: hsl1, filter : shadow1 },
{ fill: hsl1, filter : shadow1, offset: 0.45 },
{ fill: hsl2, filter : shadow2, offset: 0.50 },
{ fill: hsl2, filter : shadow2, offset: 0.95 },
{ fill: hsl1, filter : shadow1 },
], {
duration: 10000, //ミリ秒
iterations: Infinity, //繰り返し回数
}
);
arrFunc.push( func01, func02, func03);
});
//見えている時だけ動かす------------------
const switching = (e) => {
if( e[0].isIntersecting ){ //見えてる時
arrFunc.forEach((func) => {
func.play();
});
}else{ //見えてない時
arrFunc.forEach((func) => {
func.pause();
});
}
}
//見えているかどうか(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を使って作成しています。
概要
- 雪の画像を貼り付けて、ランダムに配置
- x,y,z方向に移動させる
見本
<div class="wrap js-wrap-snow">
<canvas class="canvas js-canvas-snow"></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-snow");
let wrapWidth = wrap.clientWidth;
let wrapHeight = wrap.clientHeight;
// レンダラー------------------
const renderer = new THREE.WebGLRenderer({
canvas: document.querySelector(".js-canvas-snow"),
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);
// camera.aspect = wrapWidth / wrapHeight;
// camera.updateProjectionMatrix();
}
//一定時間毎に処理------------------
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-seiretu">
<canvas class="canvas js-canvas-seiretu"></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-seiretu");
let wrapWidth = wrap.clientWidth;
let wrapHeight = wrap.clientHeight;
// レンダラー------------------
const renderer = new THREE.WebGLRenderer({
canvas: document.querySelector(".js-canvas-seiretu"),
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 ipn = ( num - i ) / num;
colors.push(
Math.round( ipn * 90 + 305 ) / 360, // 色相
Math.round( ipn * 20 + 40 ) / 100, // 彩度
Math.round( ipn * 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 == 200 ){
position = positions.sp; //球
update = true;
} else if( count == 400 ){
position = positions.hx; //螺旋
update = true;
} else if( count == 600 ){
position = positions.cb; // 立方体
update = true;
} else if( count == 800 ){
position = positions.rd; // ランダム
update = true;
count = 0;
}
if( update ){
for ( let i = 0; i < num; i ++ ) {
gsap.to( group.children[ i ].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);
// camera.aspect = wrapWidth / wrapHeight;
// camera.updateProjectionMatrix();
}
//一定時間毎に処理------------------
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();
参考
threejs examples #css3d_sprites
素のJavaScriptだけでアニメーションを実装するWeb Animations API
関連記事
マウスの位置に合わせて要素を動かす(HTML/CSS/JS)
この記事は役に立ちましたか?
送信中