JavaScript | 雪が降るアニメーションサンプル

2023-03-01JavaScript アニメーション サンプル集,JavaScript

JavaScript | 雪が降るアニメーションサンプル

JavaScriptで指定したHTML要素内に雪を降らせるアニメーションです。

過去にCSSで実装したものを紹介していますが、今回はJavaScriptにてカスタマイズが容易にできるようにしてみました。

関連:CSSのみで雪が降るアニメーションサンプル | ONE NOTES

雪が降るJavaScriptアニメーションの使い方

以下は雪が降るJavaScriptアニメーションの動作サンプルになります。
オプションを指定する事で、雪の量や、速さ、積雪や発光の有無などをカスタマイズができるようにしてあります。

See the Pen JavaScript | Drop Rain Animation by yochans (@yochans) on CodePen.

雪が降るアニメーションのJavaScriptコードは「drop-snow-anim.js」として以下の場所に置いてあります。

ダウンロード:drop-snow-anim.js(v.1.0)

ダウンロードしたjsファイルを読み込む、または既存のJavaScriptコードに貼り付けて利用できます。

<script src="drop-snow-anim.js"></script>

動作サンプルでは以下のHTMLを作成しています。
<div>要素の「container」内に雪を降らせています。
画像のありなしや要素のサイズなど、変更しても対応できているはずです。

<div id="container"></div>

CSSは以下の通りです。
「aspect-ratio」でアスペクト比を固定しています。

#container {
	width: 100%;
	aspect-ratio: 3 / 1;
}

dropSnowAnim()という関数を実行する事で、指定したHTML要素に雪を降らせます。
オプションを指定する事でカスタマイズが可能です。

※ 積雪を無効にすると、雪の落下速度が少し早くなります。

//////////////////////////// call function

let option = {
	target_element: '#container', // taget HTML element
	where_to_insert: 'img', // Insert after the specified element in target_element. If unspecified, insert at the end of the target_element. ('img' 'p' '#id' etc)
	snow_amount: 5, // Amount of snow (min 1 max 10)
	snow_speed: 5, // Speed of snow (min 1 max 10)
	snow_color: '#FFF', // Color of snow (hex, rgba, name)
	snow_accumulation: true, // Whether or not there is snow accumulation.
	snow_emission_of_light: true, // Emission of light from snow.
	animation_time: 600 // Animation time (s)
};

dropSnowAnim(option);
オプション内容初期値
target_element雪を降らすHTML要素body
where_to_inserttarget_element内の指定した要素の次に挿入
未指定の場合はtarget_element内の最後に挿入
('img’ 'p’ '#id’ etc)
未指定
snow_amount雪の量 (min 1 max 10)5
snow_speed雪の速さ (min 1 max 10)5
snow_color雪の色 (hex, rgba , name)#FFF
snow_accumulation積雪するかどうかtrue
snow_emission_of_light雪が発光するかどうかtrue
animation_timeアニメーション時間 (s)60
dropSnowAnim()のオプション
  • 雪の色を変更できます。
  • 量が多く、積雪と発光が有効ですと環境によって重くなる可能性があります。
  • アニメーション時間経過後、アニメーションは停止します。指定に制限はありません。

雪が降るアニメーションのJavaScriptコード

以下は雪が降るアニメーションの実行部分のJavaScriptコードになります。

// drop rain animation function
const dropRainAnim = (option) => {
	// default option
	let default_option = {
		target_element: 'body', // taget HTML element
		where_to_insert: '', // Insert after the specified element in target_element. If unspecified, insert at the end of the target_element. ('img' 'p' '#id' etc)
		rain_amount: 5, // Amount of rain (min 1 max 10)
		rain_speed: 5, // Speed of rain (min 1 max 10)
		rain_color: '#FFF', // Color of rain (hex, rgba, name)
		rain_width: 1, // Width of rain
		rain_height: 5, // Length of rain
		rain_angle: 15, // Angle of rain (min -90 max 90)
		bg_shade: '#000', // Background shade (hex, rgba, name)
		bg_darkness: 5, // Adjust background brightness (min 0.0 max 10.0)
		splat: false, // Rain splat
		lightning: false, // Lightning
		lightning_color: '#FFF', // Color of lightning
		animation_time: 60 // Animation time (s)
	};

	// merge option
	let op = Object.assign(default_option, option);

	// whether the target element exists
	if (!document.querySelector(op.target_element)) {
		console.log('no target element.');
		return;
	}

	// target element
	let target_element = document.querySelector(op.target_element);
	target_element.style.position = 'relative';
	target_element.style.overflow = 'hidden';

	// Insert after the specified element
	let insert_after_element = '';
	if (op.where_to_insert != '') {
		insert_after_element = target_element.querySelector(op.where_to_insert);
	}

	// main container
	let container = document.createElement('div');
	if (!insert_after_element) {
		target_element.appendChild(container);
	} else {
		insert_after_element.after(container);
	}
	container.style.position = 'absolute';
	container.style.top = 0;
	container.style.left = 0;
	container.style.width = '100%';
	container.style.height = '100%';
	container.style.overflow = 'hidden';

	// bg
	let container_bg = document.createElement('div');
	container.appendChild(container_bg);
	container_bg.style.position = 'absolute';
	container_bg.style.width = '100%';
	container_bg.style.height = '100%';
	container_bg.style.backgroundColor = op.bg_shade;
	container_bg.style.opacity = op.bg_darkness / 10;

	// rain container
	let rain_container = document.createElement('div');
	container.appendChild(rain_container);
	rain_container.style.position = 'absolute';
	rain_container.style.width = '100%';
	rain_container.style.height = '100%';
	rain_container.style.transform = `rotate(${op.rain_angle}deg)`;

	// rain clone
	let rain = document.createElement('div');
	rain.style.position = 'absolute';
	rain.style.top = '-100%';
	rain.style.left = 0;
	rain.style.width = `${op.rain_width}px`;
	rain.style.height = `${op.rain_height * 20}px`;
	rain.style.opacity = 0.7;
	rain.style.backgroundColor = op.rain_color;

	// splat container
	let splat_container = document.createElement('div');
	container.appendChild(splat_container);
	splat_container.style.position = 'absolute';
	splat_container.style.width = '100%';
	splat_container.style.height = '100%';

	// splat clone
	let splat = document.createElement('div');
	splat.style.position = 'absolute';
	splat.style.borderRadius = '50%';
	splat.style.backgroundColor = op.rain_color;

	let count = 0;
	op.animation_time *= 60;
	const update = () => {
		let rand1 = Math.floor(Math.random() * 100);
		let rand2 = Math.floor(Math.random() * 100);

		if (count % (11 - op.rain_amount) == 0) {
			// rain
			let rain_clone = rain.cloneNode();
			rain_clone.style.left = `${rand1}%`;
			rain_container.appendChild(rain_clone);

			let rain_anim = rain_clone.animate(
				[{ top: `-${op.rain_height * 50}px` }, { top: `${rand2 * 2}%` }],
				{
					duration: 1400 / op.rain_speed,
					iterations: 2
				}
			);

			rain_anim.onfinish = (event) => {
				rain_clone.remove();
			};

			// splat
			if (op.splat == true) {
				let splat_clone = splat.cloneNode();
				splat_clone.style.top = `${rand2 + 0}%`;
				splat_clone.style.left = `${rand1}%`;
				splat_clone.style.width = `${rand2 + 70}px`;
				splat_clone.style.height = `${rand2 / 2}px`;
				splat_container.appendChild(splat_clone);

				let splat_anim = splat_clone.animate(
					[
						{ transform: 'scale(0)', opacity: 0 },
						{ transform: 'scale(1)', opacity: 0.15 },
						{ transform: 'scale(2)', opacity: 0 }
					],
					{
						duration: 500
					}
				);

				splat_anim.onfinish = (event) => {
					splat_clone.remove();
				};
			}
		}

		// lightning
		if (op.lightning == true && rand1 <= 1 && rand2 <= 50) {
			container_bg.style.backgroundColor = op.lightning_color;
			container_bg.style.opacity = 0.3;
		} else if (rand1 <= 80) {
			container_bg.style.backgroundColor = op.bg_shade;
			container_bg.style.opacity = op.bg_darkness / 10;
		}

		// stop or run animation
		count++;
		if (op.animation_time >= count) {
			requestAnimationFrame(update);
		} else {
			cancelAnimationFrame(update);
		}
	};

	update();
};