JavaScriptとCSSで揺れながら落下する処理を実装する

雪が降っているような表現をしてみたかったので、JavaScriptとCSSを使って揺れながら落下する動きを実装してみました。

サンプルコード

背景全体に表示されるようにしてみます。
HTMLに雪を追加するエリアを記述します。

HTML

<div id="snowarea"></div>

この中にJavaScriptで雪の要素を追加していきます。

CSSで雪のサイズや揺れる動きのパターンを作成します。
基本的な動きは同じで数値を変えるのみなので、Sassのループで作成します。

Sass

#snowarea {
	position: fixed;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
}
$snowMinW: 10; // 雪の最小値
$snowPattern: 3; // 雪のサイズパターン
$snowStep: 2; // 雪を大きくするステップ
@for $i from 0 through $snowPattern {
	.snow#{$i} {
		position: absolute;
		margin-top: -#{($snowMinW + $snowPattern * $snowStep)}px;
		width: #{$snowMinW + $i * $snowStep}px;
		height: #{$snowMinW + $i * $snowStep}px;
		border-radius: #{($snowMinW + $i * $snowStep) / $snowStep}px;
		background: #ffffff;
		animation: ease-in-out infinite, linear;
		box-shadow: 0 0 3px 2px #ffffff;
	}
}

$swingMin: 2; // 雪の揺れの最小値
$swingPattern: 15; // 雪の揺れのパターン
$swingStep: 1; // 雪の揺れを大きくするステップ
@for $i from 0 through $swingPattern {
	@keyframes snowX#{$i} {
		0%   {	transform: translateX(0px);	}
		50%  {	transform: translateX(#{$swingMin + $i * $swingStep}px);	}
		100% {	transform: translateX(0px);	}
	}
}
@keyframes snowY {
	0%   {	top: 0%;	}
	100% {	top: 100%;	margin-top: 0;	}
}

雪の大きさと動きは最小値とパターン数、いくつずつ大きくしていくかを設定して、ループで設定しています。
雪の動きは左右に揺れる動きと縦に落下する動きの2パターン使っていて、そのうちの左右の動きのみパターンを複数用意しています。

どのパターンを使うかと落下のスピードなどはJavaScriptでランダムに決定します。

JavaScript

var snowPattern = 3; // 雪のサイズパターン(0~3)
var swingPattern = 15; // 雪の揺れのパターン(0~15)
var addSnowSpeed = 1200; // 雪を追加する間隔
var fallSpeedMin = 40; // 落下スピードの最小(秒速)
var fallSpeedMax = 80; // 落下スピードの最大(秒速)

var snowarea = document.getElementById('snowarea'); // 雪を追加するエリア
var snowArr = []; // 雪を追加する配列
var snowCount = 0; // 雪をいくつ追加したかカウント
// 一定時間毎に雪を追加
setInterval(function() {
	snowArr[snowCount] = createSnow(snowarea);
	snowarea.appendChild(snowArr[snowCount]);
	removeSnow(snowarea, snowArr[snowCount]);
	snowCount++;
}, addSnowSpeed);

// 雪を作成する関数
function createSnow(area) {
	// ブラウザの幅と高さを取得
	var areaWidth = area.clientWidth;
	var areaHeight = area.clientHeight;

	// 雪で使用する各値を取得
	var className = 'snow' + getRandomInt(0, snowPattern);
	var animationName = 'snowX' + getRandomInt(0, swingPattern);
	var swingSpeed = getRandomInt(1500, 3000) + 'ms';
	var fallSpeed = getRandomInt(Math.round(areaHeight / fallSpeedMax), Math.round(areaHeight / fallSpeedMin)) + 's';
	var StartPosition = getRandomInt(0, areaWidth) + 'px';

	// 雪の作成
	var snow = document.createElement('div');
	snow.className = className;
	snow.style.left = '';
	snow.setAttribute('style', 'left: ' + StartPosition + '; -webkit-animation-name: ' + animationName + ', snowY; animation-name: ' + animationName + ', snowY; -webkit-animation-duration: ' + swingSpeed + ', ' + fallSpeed + '; animation-duration: ' + swingSpeed + ', ' + fallSpeed + ';');

	// 作成した雪を返す
	return snow;
}

// 落下し終わった雪を削除する関数
function removeSnow(area, snow) {
	setTimeout(function() {
		snow.parentNode.removeChild(snow);
	}, Math.round(area.clientHeight / fallSpeedMin) * 1000);
}

// minからmaxまでの乱整数を返す関数
function getRandomInt(min, max) {
	return Math.floor( Math.random() * (max - min + 1) ) + min;
}

これで背景全体に雪が降っているような動きが実装できました。
揺れながら落下するデモページ
 

サンプルコード2

先ほどのサンプルでは背景全体に表示されるようにしましたが、次はコンテンツ部分に被らないように実装してみます。
雪を追加するエリアをコンテンツの左側用と右側用の2つ用意します。

HTML

<div id="snowareaLeft"></div>
<div id="snowareaRight"></div>

左右のエリア用のCSSを記述します。
雪の部分は先ほどと同じなので省略します。

Sass

#snowareaLeft {
	position: fixed;
	top: 0;
	left: 0;
	height: 100%;
}
#snowareaRight {
	position: fixed;
	top: 0;
	right: 0;
	height: 100%;
}

JavaScriptのベース部分は同じですが、雪を追加する処理を一つ複製して追加しているのと、リサイズ時に左右のエリアの幅を調整する処理を入れています。

JavaScript

var snowPattern = 3; // 雪のサイズパターン(0~3)
var swingPattern = 15; // 雪の揺れのパターン(0~15)
var addSnowSpeed = 1500; // 雪を追加する間隔
var fallSpeedMin = 40; // 落下スピードの最小(秒速)
var fallSpeedMax = 80; // 落下スピードの最大(秒速)

var snowareaLeft = document.getElementById('snowareaLeft'); // 雪を追加するエリア1
var snowArr = []; // 雪を追加する配列
var snowCount = 0; // 雪をいくつ追加したかカウント
// 一定時間毎に雪を追加
setInterval(function() {
	snowArr[snowCount] = createSnow(snowareaLeft);
	snowareaLeft.appendChild(snowArr[snowCount]);
	removeSnow(snowareaLeft, snowArr[snowCount]);
	snowCount++;
}, addSnowSpeed);

var snowareaRight = document.getElementById('snowareaRight'); // 雪を追加するエリア2
var snowArr2 = []; // 雪を追加する配列
var snowCount2 = 0; // 雪をいくつ追加したかカウント
// 一定時間毎に雪を追加
setInterval(function() {
	snowArr2[snowCount2] = createSnow(snowareaRight);
	snowareaRight.appendChild(snowArr2[snowCount2]);
	removeSnow(snowareaRight, snowArr2[snowCount2]);
	snowCount2++;
}, addSnowSpeed);

areaWidth();
window.addEventListener('resize', areaWidth);

// 雪を降らせるエリアの幅調整をする関数
function areaWidth() {
	// ブラウザ幅とコンテンツ幅から左右のエリアの幅を決める
	var windowW = window.innerWidth;
	var contentsW = document.getElementById('contents').clientWidth;
	var areaWidth = (windowW - contentsW) / 2;

	// 表示領域が100pxより広い場合は幅を調整して表示
	if(areaWidth > 100) {
		snowareaLeft.style.display = 'block';
		snowareaRight.style.display = 'block';
		snowareaLeft.style.width = areaWidth + 'px';
		snowareaRight.style.width = areaWidth + 'px';
	// 表示領域が100px以下の場合は非表示にする
	} else {
		snowareaLeft.style.display = 'none';
		snowareaRight.style.display = 'none';
	}
}

// 雪を作成する関数
function createSnow(area) {
	// ブラウザの幅と高さを取得
	var areaWidth = area.clientWidth;
	var areaHeight = area.clientHeight;

	// 雪で使用する各値を取得
	var className = 'snow' + getRandomInt(0, snowPattern);
	var animationName = 'snowX' + getRandomInt(0, swingPattern);
	var swingSpeed = getRandomInt(1500, 3000) + 'ms';
	var fallSpeed = getRandomInt(Math.round(areaHeight / fallSpeedMax), Math.round(areaHeight / fallSpeedMin)) + 's';
	var StartPosition = getRandomInt(0, areaWidth) + 'px';

	// 雪の作成
	var snow = document.createElement('div');
	snow.className = className;
	snow.style.left = '';
	snow.setAttribute('style', 'left: ' + StartPosition + '; -webkit-animation-name: ' + animationName + ', snowY; animation-name: ' + animationName + ', snowY; -webkit-animation-duration: ' + swingSpeed + ', ' + fallSpeed + '; animation-duration: ' + swingSpeed + ', ' + fallSpeed + ';');

	// 作成した雪を返す
	return snow;
}

// 落下し終わった雪を削除する関数
function removeSnow(area, snow) {
	setTimeout(function() {
		snow.parentNode.removeChild(snow);
	}, Math.round(area.clientHeight / fallSpeedMin) * 1000);
}

// minからmaxまでの乱整数を返す関数
function getRandomInt(min, max) {
	return Math.floor( Math.random() * (max - min + 1) ) + min;
}

コンテンツの左右に微妙にかぶることもありますが、概ねコンテンツに被らないように調整ができました。
揺れながら落下するデモページ2
 

このエントリーをはてなブックマークに追加

関連記事

コメントを残す

メールアドレスが公開されることはありません。
* が付いている欄は必須項目です

CAPTCHA


コメントが承認されるまで時間がかかります。

2024年7月
 123456
78910111213
14151617181920
21222324252627
28293031