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

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

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

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

以前、CSSで実装したものに、新しい要素を加えてJavaScriptで作り直してみました。

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

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

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

See the Pen JavaScript by yochans (@yochans) on CodePen.

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

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

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

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

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

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

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

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

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

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)
	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: true, // Rain splat
	lightning: true, // Lightning
	lightning_color: '#FFF', // Color of lightning
	animation_time: 60 // Animation time (s)
};

dropRainAnim(option);

dropRainAnim()で利用可能なオプション設定です。
オプションの指定がないものはデフォルト値が割り当てられます。

オプション内容初期値
target_element雨を降らすHTML要素body
where_to_inserttarget_element内の指定した要素の次に挿入
未指定の場合はtarget_element内の最後に挿入
('img’ 'p’ '#id’ etc)
未指定
rain_amount雨の量 (min 1 max 10)5
rain_speed雨の速さ (min 1 max 10)5
rain_color雨の色 (hex, rgba , name)#FFF
rain_width雨の太さ (1 = 1px)1
rain_height雨の長さ(1 = 20px)5
rain_angle雨の角度 (min -90 max 90)15
bg_shade背景の色合い (hex, rgba , name)#000
bg_darkness背景の明るさ調整 (min 0.0 max 10.0)5
splat雨脚false
lightning雷光false
lightning_color雷光の色 (hex, rgba , name)#FFF
animation_timeアニメーション時間 (s)60
dropRainAnim()のオプション

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

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

対象のHTML要素が見つからない場合は停止します。

// 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();
};