UNPKG

immaterial-design-ripple

Version:
314 lines (241 loc) 11.8 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _events = require('events'); var _events2 = _interopRequireDefault(_events); var _bluebird = require('bluebird'); var _bluebird2 = _interopRequireDefault(_bluebird); var _utility = require('./utility'); var util = _interopRequireWildcard(_utility); var _json = require('json5'); var _json2 = _interopRequireDefault(_json); var _objectAssign = require('object-assign'); var _objectAssign2 = _interopRequireDefault(_objectAssign); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var ImdRipple = function (_EventEmitter) { _inherits(ImdRipple, _EventEmitter); _createClass(ImdRipple, null, [{ key: 'bindOnLoad', /** * ページの読み込み時にインスタンスを自動で生成する * * @static * @public * @method ImdRipple.bindOnLoad */ value: function bindOnLoad(selector) { var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; return new _bluebird2.default(function (resolve) { window.addEventListener('load', function () { var elements = [].slice.call(document.querySelectorAll(selector)); resolve(elements.map(function (element) { return new ImdRipple(element, options); })); }); }); } /** * 指定の要素のクリック時にアニメーションするイベントを追加する * * @class ImdRipple * @constructor * @param {Element} element - クリックイベントを監視する要素。アニメ時子要素としてCanvasを追加する * @param {Object} [options] - this.playの引数 */ }, { key: 'util', /** * アニメーションで使用するユーティリティ関数群の参照 * * @static * @public * @property ImdRipple.util */ get: function get() { return util; } }]); function ImdRipple(element) { var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; _classCallCheck(this, ImdRipple); /** * @public * @property {HTMLElement} element */ var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(ImdRipple).call(this)); _this.element = element; // 親要素と全く同じ大きさのcanvas要素であることを期待する // そのため、position:staticプロパティを使用しない var position = window.getComputedStyle(element).getPropertyValue('position'); if (position === 'static') { _this.element.style.position = 'relative'; } // mouseup/touchendでキャンバスの透明化を開始する _this.element.addEventListener('mousedown', function (event) { if (event.which !== 1) { // only left click return; } var _this$element$getBoun = _this.element.getBoundingClientRect(); var left = _this$element$getBoun.left; var top = _this$element$getBoun.top; var x = Math.floor(event.clientX - left); var y = Math.floor(event.clientY - top); _this.emit('begin'); _this.play(x, y, (0, _objectAssign2.default)({ exitBefore: 'mouseup' }, options)).then(function () { _this.emit('end'); }); }); _this.element.addEventListener('touchstart', function (event) { var _this$element$getBoun2 = _this.element.getBoundingClientRect(); var left = _this$element$getBoun2.left; var top = _this$element$getBoun2.top; var x = Math.floor(event.changedTouches[0].clientX - left); var y = Math.floor(event.changedTouches[0].clientY - top); _this.emit('begin'); _this.play(x, y, (0, _objectAssign2.default)({ exitBefore: 'touchend' }, options)).then(function () { _this.emit('end'); }); }); return _this; } /** * this.elementに直接定義したオプションを返す * * @public * @method getOptions * @param {String} attrName 取得し、json5としてパースする属性名 * @return {Object} options オプション */ _createClass(ImdRipple, [{ key: 'getOptions', value: function getOptions() { var attrName = arguments.length <= 0 || arguments[0] === undefined ? 'imd-options' : arguments[0]; return _json2.default.parse(this.element.getAttribute(attrName) || '{}'); } /** * コンストラクタの要素内で波形アニメーションを再生する * * @public * @method ImdRipple#play * @param {Number} [x=auto] 波形アニメーションの始点x * @param {Number} [y=auto] 波形アニメーションの始点y * @param {Object} [options] ImdRipple.rippleで使用する引数 * @param {String|Bool} [options.exitBefore] canvasを破棄するタイミングの指定 * @return {Promise} ImdRipple.play参照 */ }, { key: 'play', value: function play(x, y) { var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; var opts = (0, _objectAssign2.default)({ exitBefore: true }, // auto this.getOptions(), options); var _element$getBoundingC = this.element.getBoundingClientRect(); var width = _element$getBoundingC.width; var height = _element$getBoundingC.height; var playX = x === undefined ? Math.floor(width / 2) : x; var playY = y === undefined ? Math.floor(height / 2) : y; var animation = ImdRipple.play(playX, playY, width, height, opts); this.element.appendChild(animation.context.canvas); var exit = void 0; if (typeof opts.exitBefore === 'string') { exit = util.promiseEvent(window, opts.exitBefore); } else if (opts.exitBefore === true) { exit = animation; // アニメーション終了時に事後処理 } return exit.then(function () { return util.transparentize(animation.context.canvas, opts); }).then(function () { return animation.stop(); }); } /** * CanvasRenderingContext2Dを作成して波形アニメーションを再生する * 全てのピクセルの描写を終えるまでcanvasを更新し続ける * キャンバスが大きいほど負荷が高いので、更新の必要がなければ停止する * 全てのピクセルが描写した時か、promise.stopを実行した時に、fulfillする * * @static * @public * @method ImdRipple.play * @param {Number} x 波形アニメーションの始点x * @param {Number} y 波形アニメーションの始点y * @param {Number} width 波形アニメーションの幅 * @param {Number} height 波形アニメーションの高さ * @param {Object} [options] * @param {Number} [options.pixelSize=height/15] ピクセル1粒の大きさ * @return {Promise<CanvasRenderingContext2D>} animation 独自の2プロパティを持つ */ }], [{ key: 'play', value: function play(x, y, width, height) { var options = arguments.length <= 4 || arguments[4] === undefined ? {} : arguments[4]; var opts = (0, _objectAssign2.default)({ pixelSize: Math.floor(height / 10), bitCrash: 7, pixelated: true }, options); var context = util.createContext2d(width, height, opts); var schedule = util.createRenderSchedule(x, y, width, height, opts); var imageData = util.getImageData(context.canvas); var _util$getPixelColor = util.getPixelColor(opts.color); var _util$getPixelColor2 = _slicedToArray(_util$getPixelColor, 4); var r = _util$getPixelColor2[0]; var g = _util$getPixelColor2[1]; var b = _util$getPixelColor2[2]; var a = _util$getPixelColor2[3]; var promise = new _bluebird2.default(function (resolve) { var frame = 0; var render = function render() { if (promise.disabled) { return resolve(context); } var rendered = true; var index = 0; for (var i = 0; i < context.canvas.height; i++) { for (var j = 0; j < context.canvas.width; j++) { var pixelX = Math.floor(j / schedule.pixelSize); var pixelY = Math.floor(i / schedule.pixelSize); var show = schedule.data[pixelY][pixelX] <= frame; if (show === false) { rendered = false; } imageData.data[index + 0] = r; imageData.data[index + 1] = g; imageData.data[index + 2] = b; imageData.data[index + 3] = show ? a : 0; index += 4; } } context.putImageData(imageData, 0, 0); if (rendered) { return resolve(context); } frame += 1; return util.requestAnimationFrame(render); }; util.requestAnimationFrame(render); }); // FIXME: // Promiseとcontextを同時返したい。 // (mouseupイベントで透明化を始めたいので) promise.context = context; promise.stop = function stopAnimation() { promise.disabled = true; return this; }; return promise; } }]); return ImdRipple; }(_events2.default); exports.default = ImdRipple; module.exports = exports['default'];