immaterial-design-ripple
Version:
HTML5 Canvas based pixelated ripple effect
307 lines (269 loc) • 14.4 kB
HTML
<html>
<head>
<meta charset="utf-8">
<base data-ice="baseUrl" href="../../">
<title data-ice="title">src/utility.js | API Document</title>
<link type="text/css" rel="stylesheet" href="css/style.css">
<link type="text/css" rel="stylesheet" href="css/prettify-tomorrow.css">
<script src="script/prettify/prettify.js"></script>
<script src="script/manual.js"></script>
</head>
<body class="layout-container" data-ice="rootContainer">
<header>
<a href="./">Home</a>
<a href="identifiers.html">Reference</a>
<a href="source.html">Source</a>
<a data-ice="repoURL" href="https://github.com/immaterial-design/immaterial-design-ripple" class="repo-url-github">Repository</a>
<div class="search-box">
<span>
<img src="./image/search.png">
<span class="search-input-edge"></span><input class="search-input"><span class="search-input-edge"></span>
</span>
<ul class="search-result"></ul>
</div>
</header>
<nav class="navigation" data-ice="nav"><div>
<ul>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/index.js~ImdRipple.html">ImdRipple</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-function">F</span><span data-ice="name"><span><a href="function/index.html#static-function-createContext2d">createContext2d</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-function">F</span><span data-ice="name"><span><a href="function/index.html#static-function-createRenderSchedule">createRenderSchedule</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-function">F</span><span data-ice="name"><span><a href="function/index.html#static-function-getImageData">getImageData</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-function">F</span><span data-ice="name"><span><a href="function/index.html#static-function-getPixelColor">getPixelColor</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-function">F</span><span data-ice="name"><span><a href="function/index.html#static-function-getTimingFunction">getTimingFunction</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-function">F</span><span data-ice="name"><span><a href="function/index.html#static-function-promiseEvent">promiseEvent</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-function">F</span><span data-ice="name"><span><a href="function/index.html#static-function-requestAnimationFrame">requestAnimationFrame</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-function">F</span><span data-ice="name"><span><a href="function/index.html#static-function-transparentize">transparentize</a></span></span></li>
</ul>
</div>
</nav>
<div class="content" data-ice="content"><h1 data-ice="title">src/utility.js</h1>
<pre class="source-code line-number raw-source-code"><code class="prettyprint linenums" data-ice="content">import Promise from 'bluebird';
import easingJs from 'easing-js';
import objectAssign from 'object-assign';
/**
* 利用可能な非同期関数でcallbackを実行する
*
* @function requestAnimationFrame
* @param {Function} [callback]
* @return undefined
*/
export function requestAnimationFrame(callback) {
return window.requestAnimationFrame(callback);
}
/**
* 指定した要素のイベントを待つプロミスを返す
*
* @function promiseEvent
* @param {Element} target イベントを取得する要素
* @param {String} eventName 取得するイベント名
* @return {Promise<EventTarget>} deferredEvent 取得したイベント
*/
export function promiseEvent(target, eventName) {
return new Promise((resolve) => {
const onceListener = (event) => {
target.removeEventListener(eventName, onceListener);
resolve(event);
};
target.addEventListener(eventName, onceListener);
});
}
/**
* 指定した大きさのcontext2dを返す
*
* @function createContext2d
* @param {Number} width contextの幅
* @param {Number} height contextの高さ
* @param {Object} [options]
* @param {Object} [options.pixelated=true] アンチエイリアスを切る
* @return {CanvasRenderingContext2D}
*/
export function createContext2d(width, height, options = {}) {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
canvas.style.position = 'absolute';
canvas.style.top = 0;
canvas.style.right = 0;
canvas.style.bottom = 0;
canvas.style.left = 0;
const context = canvas.getContext('2d');
if (options.pixelated) {
context.mozImageSmoothingEnabled = false;
context.msImageSmoothingEnabled = false;
context.imageSmoothingEnabled = false;
}
return context;
}
/**
* contextと同じ大きさの空のimageDataを返す
*
* @function getImageData
* @param {HTMLCanvasElement} canvas 大きさの基準となるcanvas
* @return {ImageData}
*/
export function getImageData(canvas) {
const { width, height } = canvas;
const newContext = document.createElement('canvas').getContext('2d');
newContext.canvas.width = width;
newContext.canvas.height = height;
return newContext.getImageData(0, 0, width, height);
}
/**
* canvasを透明化、opacity:0でcanvasを破棄
*
* @function transparentize
* @param {Element} element 透明化させ、破棄する要素
* @param {Object} [options]
* @param {Number} [options.opacityStep=0.02] 1フレームの透明化進行度
* @return {Promise<null>} animation 要素破棄時にfullfill
*/
export function transparentize(element, options = {}) {
const elementStyle = element.style;
const opts = objectAssign({
opacityStep: 0.02,
}, options);
return new Promise((resolve) => {
let opacity = 1;
const render = () => {
opacity -= opts.opacityStep;
if (opacity <= 0) {
elementStyle.opacity = 0;
return resolve();
}
elementStyle.opacity = opacity;
return requestAnimationFrame(render);
};
requestAnimationFrame(render);
}).then(() => {
if (element.parentNode) {
element.parentNode.removeChild(element);
}
});
}
/**
* easingJsで定義された関数名であれば、その関数を返し
* 引数が関数であれば、そのまま返す
* それ以外はnull
*
* @function getTimingFunction
* @param {String|Function} name EasingJsの関数名か、独自で関数を定義
* @return {Function|null} timingFunction (t,b,c,d)を受け取るイージング関数。未定義の関数名ならnull
*/
export function getTimingFunction(name = 'easeInBack') {
if (typeof name === 'function') {
return name;
}
if (easingJs[name]) {
return easingJs[name];
}
return null;
}
/**
* 指定した大きさのimageDataを作成し
* 波形アニメーションとして表示するフレーム番号を計算する
* x,yを始点とする
*
* 返される配列の値は大きさからpixelSizeを割ったもの。
*
* @function createRenderSchedule
* @param {Number} x 波形アニメーションの始点x
* @param {Number} y 波形アニメーションの始点y
* @param {Number} width 波形アニメーションの幅
* @param {Number} height 波形アニメーションの高さ
* @param {Object} [options]
* @param {Number} [options.pixelSize] ピクセル1粒の大きさ
* @param {Number} [options.bitCrash=null] 境界にノイズを入れる、値はノイズの強さ
* @param {String|Function} [options.timingFunction='easeInQuint'] フレーム番号のイージング関数名
* @return {Object} RenderSchedule
* @return {Array} RenderSchedule.data yとxからなる二次元配列。表示するフレーム番号を値に持つ
* @return {Number} RenderSchedule.width ピクセルの横の個数
* @return {Number} RenderSchedule.height ピクセルの縦の個数
* @return {Number} RenderSchedule.pixelSize ピクセル1粒の大きさ
* @return {Function|null} RenderSchedule.easedBy フレーム番号の調整に使用した関数
*/
export function createRenderSchedule(x, y, width, height, options = {}) {
const opts = Object.create(options);
if (opts.pixelSize === undefined) {
opts.pixelSize = 1;
}
const dataWidth = Math.ceil(width / opts.pixelSize);
const dataHeight = Math.ceil(height / opts.pixelSize);
const data = [];
// ピクセルごとの表示を開始するフレーム番号を定義する
let c = 0;// maxFrame
for (let i = 0; i < dataHeight; i++) {
if (data[i] === undefined) {
data[i] = [];
}
for (let j = 0; j < dataWidth; j++) {
const originalX = opts.pixelSize * j;
const originalY = opts.pixelSize * i;
// x, yからの距離をピクセル基準で求める
const distanceX = Math.abs(originalX - x);
const distanceY = Math.abs(originalY - y);
const distance = Math.sqrt(Math.pow(distanceX, 2) + Math.pow(distanceY, 2));
const showFrame = Math.floor(distance / opts.pixelSize);
if (c < showFrame) {
c = showFrame;
}
data[i].push(showFrame);
}
}
// アニメーションの緩急を変更する
// http://d.hatena.ne.jp/nakamura001/20111117/1321539246
const timingFunction = getTimingFunction(opts.timingFunction);
if (timingFunction) {
const d = 1;
for (let i = 0; i < data.length; i++) {
for (let j = 0; j < data[i].length; j++) {
const b = data[i][j];// showFrame
const t = c > 0 ? (b / c) : 0;// distanceRate
data[i][j] = Math.floor(timingFunction(t, b, c, d));
// 5フレーム以降は境界部分のジャギーを目立たせる(ささくれさせる)
if (opts.bitCrash > 1 && b > 5) {
data[i][j] += Math.floor(opts.bitCrash * Math.random());
}
}
}
}
return {
data,
width: dataWidth,
height: dataHeight,
pixelSize: opts.pixelSize,
easedBy: timingFunction,
};
}
/**
* 指定したcolorNameのrgbaを返す(CanvasRenderingContext2D経由)
*
* @function getPixelColor
* @param {String} colorName CanvasRenderingContext2D.fillStyleの値
* @return {Array} color [r,g,b,a]
*/
export function getPixelColor(colorName = 'rgba(0,0,0,.3)') {
const context = document.createElement('canvas').getContext('2d');
context.canvas.width = 1;
context.canvas.width = 1;
context.fillStyle = colorName;
context.fillRect(0, 0, 1, 1);
// splat構文を使用するとエラーになるので、配列に変換する
// const [r,g,b,a] = document.createElement('canvas').getContext('2d').getImagedata(...).data
// => TypeError: Invalid attempt to destructure non-iterable instance
return [].slice.call(context.getImageData(0, 0, 1, 1).data);
}
</code></pre>
</div>
<footer class="footer">
Generated by <a href="https://esdoc.org">ESDoc<span data-ice="esdocVersion">(0.4.7)</span></a>
</footer>
<script src="script/search_index.js"></script>
<script src="script/search.js"></script>
<script src="script/pretty-print.js"></script>
<script src="script/inherited-summary.js"></script>
<script src="script/test-summary.js"></script>
<script src="script/inner-link.js"></script>
<script src="script/patch-for-local.js"></script>
</body>
</html>