magic-snowflakes
Version:
Falling snowflakes
434 lines (345 loc) • 14.5 kB
JavaScript
/*! Snowflakes | © 2018 Denis Seleznev | MIT License | https://github.com/hcodes/snowflakes/ */
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.Snowflakes = factory());
}(this, (function () { 'use strict';
var animationPrefix = '';
if (typeof window !== 'undefined') {
animationPrefix = Array.prototype.slice.call(window.getComputedStyle(document.documentElement, '')).join(',').search(/,animation/) > -1 ? '' : 'Webkit';
}
/**
* Set inline style.
*
* @param {DOMElement} dom
* @param {Object} props
*/
function setStyle(dom, props) {
Object.keys(props).forEach(function (originalKey) {
var key = originalKey;
if (animationPrefix && originalKey.search('animation') > -1) {
key = animationPrefix + originalKey[0].toUpperCase() + originalKey.substr(1);
}
dom.style[key] = props[originalKey];
});
}
/**
* Show DOM element.
*
* @param {DOMElement} dom
*/
function showElement(dom) {
setStyle(dom, { display: 'block' });
}
/**
* Hide DOM element.
*
* @param {DOMElement} dom
*/
function hideElement(dom) {
setStyle(dom, { display: 'none' });
}
/**
* Get random number.
*
* @param {number} from
* @param {number} max
*
* @returns {number}
*/
function getRandom(from, max) {
return from + Math.floor(Math.random() * (max - from));
}
/**
* Linear interpolation.
*
* @param {number} x
* @param {number} x1
* @param {number} x2
* @param {number} y1
* @param {number} y2
*
* @returns {number}
*/
function interpolation(x, x1, x2, y1, y2) {
return y1 + (y2 - y1) * (x - x1) / (x2 - x1);
}
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var Flake = function () {
/**
* @constructor
*
* @param {DOMElement} container
* @param {number} containerHeight
* @param {Object} params
* @param {number} [params.count]
* @param {number} [params.speed]
* @param {boolean} [params.rotation]
* @param {number} [params.minOpacity]
* @param {number} [params.maxOpacity]
* @param {number} [params.minSize]
* @param {number} [params.maxSize]
* @param {number} [params.types]
* @param {number} [params.wind]
* @param {number} [params.zIndex]
*/
function Flake(container, containerHeight, params) {
classCallCheck(this, Flake);
var isEqual = params.minSize === params.maxSize;
this.innerSize = isEqual ? 0 : getRandom(0, Flake.maxInnerSize);
this.size = Flake.calcSize(this.innerSize, params);
var flake = document.createElement('div'),
innerFlake = document.createElement('div'),
animationProps = this.getAnimationProps(containerHeight, params),
styleProps = {
animationDelay: animationProps.animationDelay,
animationDuration: animationProps.animationDuration,
left: Math.random() * 99 + '%',
marginTop: -Math.sqrt(2) * this.size + 'px',
width: this.size + 'px',
height: this.size + 'px'
};
if (!isEqual) {
styleProps.zIndex = params.zIndex + this.size * 10;
styleProps.opacity = interpolation(this.size, params.minSize, params.maxSize, params.minOpacity, params.maxOpacity);
}
setStyle(flake, styleProps);
setStyle(innerFlake, {
animationName: 'snowflake_x_' + this.innerSize,
animationDelay: Math.random() + 's'
});
flake.classList.add('snowflake');
innerFlake.classList.add('snowflake__inner');
if (params.types) {
innerFlake.classList.add('snowflake__inner_type_' + getRandom(0, params.types));
}
if (params.wind) {
innerFlake.classList.add('snowflake__inner_wind');
}
if (params.rotation) {
innerFlake.classList.add('snowflake__inner_rotation' + (Math.random() > 0.5 ? '' : '_reverse'));
}
flake.appendChild(innerFlake);
this._elem = flake;
container.appendChild(flake);
}
/**
* Calc size.
*
* @param {number} innerSize
* @param {Object} params
*
* @returns {number}
*/
Flake.calcSize = function calcSize(innerSize, params) {
return Math.floor(interpolation(innerSize, 0, Flake.maxInnerSize, params.minSize, params.maxSize));
};
/**
* Get animation properties.
*
* @param {number} containerHeight
* @param {Object} params
*
* @returns {Object}
*/
Flake.prototype.getAnimationProps = function getAnimationProps(containerHeight, params) {
var speedMax = containerHeight / 50 / params.speed,
speedMin = speedMax / 3;
return {
animationDelay: Math.random() * speedMax + 's',
animationDuration: interpolation(this.size, params.minSize, params.maxSize, speedMax, speedMin) + 's'
};
};
/**
* Resize a flake.
*
* @param {number} containerHeight
* @param {Object} params
*/
Flake.prototype.resize = function resize(containerHeight, params) {
var props = this.getAnimationProps(containerHeight, params);
setStyle(this._elem, props);
};
/**
* Destroy a flake.
*/
Flake.prototype.destroy = function destroy() {
delete this._elem;
};
return Flake;
}();
Flake.maxInnerSize = 20;
var mainStyle = '.snowflakes_paused .snowflake,.snowflakes_paused .snowflake__inner,.snowflakes_paused .snowflake__inner:before{-webkit-animation-play-state:paused;animation-play-state:paused}.snowflakes_body{position:fixed;left:0;top:0;width:100%}.snowflake{position:absolute;-webkit-animation:snowflake_y 10s infinite linear;animation:snowflake_y 10s infinite linear;will-change:transform}.snowflake__inner{position:absolute;left:0;right:0;top:0;bottom:0}.snowflake__inner:before{position:absolute;left:0;right:0;top:0;bottom:0;content:\'\';background-size:100% 100%}.snowflake__inner_wind{-webkit-animation:snowflake_x_8 1s infinite alternate ease-in-out;animation:snowflake_x_8 1s infinite alternate ease-in-out}.snowflake__inner_rotation:before{-webkit-animation:snowflake_rotation 2s infinite linear;animation:snowflake_rotation 2s infinite linear}.snowflake__inner_rotation_reverse:before{-webkit-animation:snowflake_rotation_reverse 2s infinite linear;animation:snowflake_rotation_reverse 2s infinite linear}@-webkit-keyframes snowflake_rotation{from{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes snowflake_rotation{from{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes snowflake_rotation_reverse{from{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(-360deg);transform:rotate(-360deg)}}@keyframes snowflake_rotation_reverse{from{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(-360deg);transform:rotate(-360deg)}}';
var imagesStyle = '';
var Snowflakes = function () {
/**
* @constructor
*
* @param {Object} params
*
* @param {DOMElem} [params.container=document.body]
* @param {number} [params.count=50]
* @param {number} [params.color="#5ECDEF"]
* @param {number} [params.minOpacity=0.6]
* @param {number} [params.maxOpacity=1]
* @param {number} [params.minSize=8]
* @param {number} [params.maxSize=18]
* @param {boolean} [params.rotation=true]
* @param {number} [params.speed=1]
* @param {boolean} [params.stop=false]
* @param {number} [params.types=6]
* @param {number} [params.width=width of container]
* @param {number} [params.height=height of container]
* @param {boolean} [params.wind=true]
* @param {number} [params.zIndex=9999]
*/
function Snowflakes(params) {
var _this = this;
classCallCheck(this, Snowflakes);
this.params = this._setParams(params);
this._flakes = [];
this._isBody = this.params.container === document.body;
var container = this._container = document.createElement('div');
container.classList.add('snowflakes');
this._isBody && container.classList.add('snowflakes_body');
setStyle(container, { zIndex: this.params.zIndex });
this.params.container.appendChild(container);
if (this.params.stop) {
this.stop();
}
if (!Snowflakes._mainStyleNode) {
Snowflakes._mainStyleNode = this._injectStyle(mainStyle);
Snowflakes._count = (Snowflakes._count || 0) + 1;
}
this._winHeight = this._getWindowHeight();
this._onResize = function () {
_this._winHeight = _this._getWindowHeight();
var height = _this._height();
hideElement(container);
_this._flakes.forEach(function (flake) {
return flake.resize(height, _this.params);
});
_this._updateAnimationStyle();
showElement(container);
};
if (imagesStyle) {
this._imagesStyleNode = this._injectStyle(imagesStyle.replace(/%7Bcolor%7D/g, encodeURIComponent(this.params.color)));
}
this._animationStyleNode = this._injectStyle(this._getAnimationStyle());
window.addEventListener('resize', this._onResize, false);
for (var i = 0; i < this.params.count; i++) {
this._flakes.push(new Flake(container, this._height(), this.params));
}
}
/**
* Destroy flakes.
*/
Snowflakes.prototype.destroy = function destroy() {
this._removeStyle();
this._container && this._container.parentNode.removeChild(this._container);
delete this._container;
window.removeEventListener('resize', this._onResize, false);
this._flakes.forEach(function (flake) {
return flake.destroy();
});
delete this._flakes;
delete this.params;
};
/**
* Start CSS animation.
*/
Snowflakes.prototype.start = function start() {
this._container.classList.remove('snowflakes_paused');
};
/**
* Stop CSS animation.
*/
Snowflakes.prototype.stop = function stop() {
this._container.classList.add('snowflakes_paused');
};
Snowflakes.prototype._setParams = function _setParams(params) {
params = params || {};
var result = {};
[['color', '#5ECDEF'], ['container', document.body], ['count', 50], ['speed', 1], ['stop', false], ['rotation', true], ['minOpacity', 0.6], ['maxOpacity', 1], ['minSize', 8], ['maxSize', 18], ['types', 6], ['width'], ['height'], ['wind', true], ['zIndex', 9999]].forEach(function (item) {
var name = item[0],
defaultValue = item[1];
if (typeof defaultValue === 'boolean') {
result[name] = name in params ? params[name] : defaultValue;
} else {
result[name] = params[name] || defaultValue;
}
});
return result;
};
Snowflakes.prototype._getAnimationStyle = function _getAnimationStyle() {
var height = this._height() + this.params.maxSize + 'px';
var css = '@-webkit-keyframes snowflake_y{from{-webkit-transform:translateY(0px)}to{-webkit-transform:translateY(' + height + ');}}\n@keyframes snowflake_y{from{transform:translateY(0px)}to{transform:translateY(' + height + ')}}';
for (var i = 0; i <= Flake.maxInnerSize; i++) {
var left = (Flake.calcSize(i, this.params) - this.params.minSize) * 4 + 'px';
css += '@-webkit-keyframes snowflake_x_' + i + '{from{-webkit-transform:translateX(0px)}to{-webkit-transform:translateX(' + left + ');}}\n@keyframes snowflake_x_' + i + '{from{transform:translateX(0px)}to{transform:translateX(' + left + ')}}';
}
return css;
};
Snowflakes.prototype._updateAnimationStyle = function _updateAnimationStyle() {
this._injectStyle(this._getAnimationStyle(), this._animationStyleNode);
};
Snowflakes.prototype._injectStyle = function _injectStyle(style, styleNode) {
if (!styleNode) {
styleNode = document.createElement('style');
document.body.appendChild(styleNode);
}
if (styleNode.styleSheet) {
// IE
styleNode.styleSheet.cssText = style;
} else if ('textContent' in styleNode) {
styleNode.textContent = style;
} else {
styleNode.innerHTML = style;
}
return styleNode;
};
Snowflakes.prototype._removeStyle = function _removeStyle() {
Snowflakes._count--;
if (Snowflakes._count <= 0) {
Snowflakes._count = 0;
if (Snowflakes._mainStyleNode) {
Snowflakes._mainStyleNode.parentNode.removeChild(Snowflakes._mainStyleNode);
delete Snowflakes._mainStyleNode;
}
}
if (this._animationStyleNode) {
this._animationStyleNode.parentNode.removeChild(this._animationStyleNode);
delete this._animationStyleNode;
}
if (this._imagesStyleNode) {
this._imagesStyleNode.parentNode.removeChild(this._imagesStyleNode);
delete this._imagesStyleNode;
}
};
Snowflakes.prototype._height = function _height() {
return this.params.height || (this._isBody ? this._winHeight : this.params.container.offsetHeight + this.params.maxSize);
};
Snowflakes.prototype._getWindowHeight = function _getWindowHeight() {
var body = document.body,
docElement = document.documentElement;
var height = void 0;
if (window.innerHeight) {
height = window.innerHeight;
} else if (docElement && docElement.clientHeight) {
height = docElement.clientHeight;
} else if (body) {
height = body.clientHeight;
}
return height;
};
return Snowflakes;
}();
var snowflakes = function (params) {
return new Snowflakes(params);
};
return snowflakes;
})));