├── .DS_Store ├── src ├── .DS_Store ├── images │ ├── 002.png │ ├── 049.jpeg │ ├── 053.jpeg │ ├── 059.jpeg │ ├── 401.jpeg │ ├── cover.jpg │ └── share.jpg ├── script │ ├── .DS_Store │ ├── lib │ │ ├── .DS_Store │ │ ├── gsap │ │ │ ├── .DS_Store │ │ │ ├── plugins │ │ │ │ ├── AttrPlugin.js │ │ │ │ ├── EndArrayPlugin.js │ │ │ │ ├── RoundPropsPlugin.js │ │ │ │ ├── DirectionalRotationPlugin.js │ │ │ │ ├── CSSRulePlugin.js │ │ │ │ ├── TextPlugin.js │ │ │ │ ├── ModifiersPlugin.js │ │ │ │ ├── TEMPLATE_Plugin.js │ │ │ │ ├── ScrollToPlugin.js │ │ │ │ ├── ColorPropsPlugin.js │ │ │ │ ├── EaselPlugin.js │ │ │ │ ├── RaphaelPlugin.js │ │ │ │ ├── PixiPlugin.js │ │ │ │ └── BezierPlugin.js │ │ │ └── easing │ │ │ │ └── EasePack.js │ │ ├── Events.js │ │ └── utils.es6 │ ├── plugin.es6 │ ├── oneStrokePlugin.es6 │ ├── plugin.js │ └── onestroke.es6 ├── css │ └── onstroke.css ├── plugin.html └── onestroke.html ├── .gitignore ├── package.json ├── webpack.config.js └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/OneStroke/HEAD/.DS_Store -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/OneStroke/HEAD/src/.DS_Store -------------------------------------------------------------------------------- /src/images/002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/OneStroke/HEAD/src/images/002.png -------------------------------------------------------------------------------- /src/images/049.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/OneStroke/HEAD/src/images/049.jpeg -------------------------------------------------------------------------------- /src/images/053.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/OneStroke/HEAD/src/images/053.jpeg -------------------------------------------------------------------------------- /src/images/059.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/OneStroke/HEAD/src/images/059.jpeg -------------------------------------------------------------------------------- /src/images/401.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/OneStroke/HEAD/src/images/401.jpeg -------------------------------------------------------------------------------- /src/images/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/OneStroke/HEAD/src/images/cover.jpg -------------------------------------------------------------------------------- /src/images/share.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/OneStroke/HEAD/src/images/share.jpg -------------------------------------------------------------------------------- /src/script/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/OneStroke/HEAD/src/script/.DS_Store -------------------------------------------------------------------------------- /src/script/lib/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/OneStroke/HEAD/src/script/lib/.DS_Store -------------------------------------------------------------------------------- /src/script/lib/gsap/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/OneStroke/HEAD/src/script/lib/gsap/.DS_Store -------------------------------------------------------------------------------- /src/script/plugin.es6: -------------------------------------------------------------------------------- 1 | import OneStrokePlugin from "./OneStrokePlugin.es6"; 2 | 3 | window.OneStrokePlugin = OneStrokePlugin; 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | .temp/ 3 | .rn_temp/ 4 | node_modules/ 5 | node_modules_back/ 6 | base_back/ 7 | backup/ 8 | .DS_Store 9 | .idea 10 | .vscode/ 11 | .prettierrc 12 | npm-debug.log* 13 | yarn-error.log 14 | package-lock.json 15 | src/index.html 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "one-stroke", 3 | "version": "1.0.0", 4 | "description": "一笔画", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server --config=webpack.config.js", 8 | "build": "webpack --config=webpack.config.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/leeenx/OneStroke.git" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/leeenx/OneStroke/issues" 18 | }, 19 | "homepage": "https://github.com/leeenx/OneStroke#readme", 20 | "devDependencies": { 21 | "copy-webpack-plugin": "^5.0.5", 22 | "webpack": "^4.41.2" 23 | }, 24 | "dependencies": { 25 | "@babel/cli": "^7.7.0", 26 | "@babel/core": "^7.7.2", 27 | "@babel/node": "^7.7.0", 28 | "@babel/polyfill": "^7.7.0", 29 | "@babel/preset-env": "^7.7.1", 30 | "babel-loader": "^8.0.6", 31 | "clean-webpack-plugin": "^3.0.0", 32 | "html-webpack-plugin": "^3.2.0", 33 | "webpack-dev-server": "^3.9.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/script/lib/Events.js: -------------------------------------------------------------------------------- 1 | /* 2 | @ author: leeenx 3 | @ 事件封装 4 | @ object.on(event, fn) // 监听一个事件 5 | @ object.off(event, fn) // 取消监听 6 | @ object.once(event, fn) // 只监听一次事件 7 | @ object.dispacth(event, arg) // 触发一个事件 8 | */ 9 | 10 | export default class Events { 11 | constructor() { 12 | // 定义的事件与回调 13 | this.defineEvent = {}; 14 | } 15 | // 注册事件 16 | register(event, cb) { 17 | if(!this.defineEvent[event]) { 18 | (this.defineEvent[event] = [cb]); 19 | } 20 | else { 21 | this.defineEvent[event].push(cb); 22 | } 23 | } 24 | // 派遣事件 25 | dispatch(event, arg) { 26 | if(this.defineEvent[event]) {{ 27 | for(let i=0, len = this.defineEvent[event].length; i this.defineEvent[event].splice(i, 1), 0); 48 | break; 49 | } 50 | } 51 | } 52 | } 53 | } 54 | 55 | // once 方法,监听一次 56 | once(event, cb) { 57 | let onceCb = () => { 58 | cb && cb(); 59 | this.off(event, onceCb); 60 | } 61 | this.register(event, onceCb); 62 | } 63 | } -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/AttrPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 0.6.1 3 | * DATE: 2017-06-19 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | */ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | _gsScope._gsDefine.plugin({ 18 | propName: "attr", 19 | API: 2, 20 | version: "0.6.1", 21 | 22 | //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 23 | init: function(target, value, tween, index) { 24 | var p, end; 25 | if (typeof(target.setAttribute) !== "function") { 26 | return false; 27 | } 28 | for (p in value) { 29 | end = value[p]; 30 | if (typeof(end) === "function") { 31 | end = end(index, target); 32 | } 33 | this._addTween(target, "setAttribute", target.getAttribute(p) + "", end + "", p, false, p); 34 | this._overwriteProps.push(p); 35 | } 36 | return true; 37 | } 38 | 39 | }); 40 | 41 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 42 | 43 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 44 | (function(name) { 45 | "use strict"; 46 | var getGlobal = function() { 47 | return (_gsScope.GreenSockGlobals || _gsScope)[name]; 48 | }; 49 | if (typeof(module) !== "undefined" && module.exports) { //node 50 | require("../TweenLite.js"); 51 | module.exports = getGlobal(); 52 | } else if (typeof(define) === "function" && define.amd) { //AMD 53 | define(["TweenLite"], getGlobal); 54 | } 55 | }("AttrPlugin")); -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin') 2 | const { CleanWebpackPlugin } = require('clean-webpack-plugin') 3 | const CopyWebpackPlugin = require('copy-webpack-plugin') 4 | 5 | 6 | const openBrowser = process.argv[3] === '--silence' ? false : true 7 | 8 | module.exports = [ 9 | { 10 | mode: 'development', 11 | context: __dirname, 12 | entry: { 13 | index: [ 14 | './src/script/lib/pixi.js', 15 | './src/script/lib/gsap/TweenMax.js', 16 | './src/script/onestroke.es6' 17 | ], 18 | plugin: [ 19 | './src/script/plugin.es6' 20 | ] 21 | }, 22 | output: { 23 | path: __dirname + '/dist/script/', 24 | filename: '[name]-[hash:16].js' 25 | }, 26 | module: { 27 | rules: [ 28 | { 29 | test: function (src) { 30 | if ( 31 | src.indexOf('script/lib/pixi.js') >= 0 || 32 | src.indexOf('script/lib/gsap/TweenMax.js') >= 0 33 | ) { 34 | return false 35 | } 36 | if (/\.es6$|\.js$/.test(src)) { 37 | return true 38 | } 39 | }, 40 | use: [ 41 | { 42 | loader: 'babel-loader', 43 | options: { 44 | presets: ['@babel/env'] 45 | } 46 | } 47 | ] 48 | } 49 | ] 50 | }, 51 | plugins: [ 52 | new CleanWebpackPlugin(), 53 | new HtmlWebpackPlugin({ 54 | title: 'H5小游戏100例: 一笔画', 55 | template: './src/onestroke.html', 56 | filename: './dist/index.html', 57 | inject: false 58 | }), 59 | new HtmlWebpackPlugin({ 60 | title: 'H5小游戏100例: 生成配置', 61 | template: './src/plugin.html', 62 | filename: './dist/plugin.html', 63 | inject: false 64 | }), 65 | new CopyWebpackPlugin([ 66 | { 67 | from: './src/css/onstroke.css', 68 | to: './dist/css/onstroke.css' 69 | } 70 | ]) 71 | ], 72 | devServer: { 73 | contentBase: './dist/', 74 | open: openBrowser, 75 | openPage: './dist/index.html' 76 | }, 77 | watch: true 78 | } 79 | ] 80 | -------------------------------------------------------------------------------- /src/script/lib/utils.es6: -------------------------------------------------------------------------------- 1 | // PIXI 的扩展 2 | PIXI.DisplayObject.prototype.set = function(arg) { 3 | for(let key in arg) { 4 | this[key] = arg[key]; 5 | } 6 | } 7 | 8 | // scale 属性拍平 9 | Object.defineProperties(PIXI.DisplayObject.prototype, { 10 | scaleX: { 11 | set: function(value) { 12 | this.scale.x = value; 13 | }, 14 | get: function() { 15 | return this.scale.x; 16 | } 17 | }, 18 | scaleY: { 19 | set: function(value) { 20 | this.scale.y = value; 21 | }, 22 | get: function() { 23 | return this.scale.y; 24 | } 25 | }, 26 | pivotX: { 27 | set: function(value) { 28 | this.pivot.x = value; 29 | }, 30 | get: function() { 31 | return this.pivot.x; 32 | } 33 | }, 34 | pivotY: { 35 | set: function(value) { 36 | this.pivot.y = value 37 | }, 38 | get: function() { 39 | return this.pivot.y; 40 | } 41 | }, 42 | anchorX: { 43 | set: function(value) { 44 | this.anchor.x = value; 45 | }, 46 | get: function() { 47 | return this.anchor.x; 48 | } 49 | }, 50 | anchorY: { 51 | set: function(value) { 52 | this.anchor.y = value 53 | }, 54 | get: function() { 55 | return this.anchor.y; 56 | } 57 | } 58 | }); 59 | 60 | // 获取不带描边的boudary 61 | { 62 | let dirty = Symbol("dirty"); 63 | let getContentBox = function() { 64 | if(this[dirty] == this.dirty) return ; 65 | this[dirty] = this.dirty; // 表示已经更新 66 | let cp = this.clone(); 67 | let graphicsData = cp.graphicsData; 68 | for(let graphics of graphicsData) { 69 | graphics.lineWidth = 0; 70 | } 71 | this._cwidth = cp.width; 72 | this._cheight = cp.height; 73 | } 74 | Object.defineProperties(PIXI.Graphics.prototype, { 75 | "_cwidth": {writable: true, value: 0}, 76 | "_cheight": {writable: true, value: 0}, 77 | "cwidth": { 78 | get: function() { 79 | getContentBox.call(this); 80 | return this._cwidth; 81 | } 82 | }, 83 | "cheight": { 84 | get: function() { 85 | getContentBox.call(this); 86 | return this._cheight; 87 | } 88 | } 89 | }); 90 | } -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/EndArrayPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 0.1.3 3 | * DATE: 2017-01-17 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | */ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | _gsScope._gsDefine.plugin({ 18 | propName: "endArray", 19 | API: 2, 20 | version: "0.1.3", 21 | 22 | //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 23 | init: function(target, value, tween) { 24 | var i = value.length, 25 | a = this.a = [], 26 | start, end; 27 | this.target = target; 28 | this._mod = 0; 29 | if (!i) { 30 | return false; 31 | } 32 | while (--i > -1) { 33 | start = target[i]; 34 | end = value[i]; 35 | if (start !== end) { 36 | a.push({i:i, s:start, c:end - start}); 37 | } 38 | } 39 | return true; 40 | }, 41 | 42 | mod: function(lookup) { 43 | if (typeof(lookup.endArray) === "function") { 44 | this._mod = lookup.endArray; 45 | } 46 | }, 47 | 48 | //called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.) 49 | set: function(ratio) { 50 | var target = this.target, 51 | a = this.a, 52 | i = a.length, 53 | mod = this._mod, 54 | e, val; 55 | if (mod) { 56 | while (--i > -1) { 57 | e = a[i]; 58 | target[e.i] = mod(e.s + e.c * ratio, target); 59 | } 60 | } else { 61 | while (--i > -1) { 62 | e = a[i]; 63 | val = e.s + e.c * ratio; 64 | target[e.i] = (val < 0.000001 && val > -0.000001) ? 0 : val; 65 | } 66 | } 67 | } 68 | 69 | }); 70 | 71 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/RoundPropsPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 1.6.0 3 | * DATE: 2017-01-17 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | **/ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | var RoundPropsPlugin = _gsScope._gsDefine.plugin({ 18 | propName: "roundProps", 19 | version: "1.6.0", 20 | priority: -1, 21 | API: 2, 22 | 23 | //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 24 | init: function(target, value, tween) { 25 | this._tween = tween; 26 | return true; 27 | } 28 | 29 | }), 30 | _roundLinkedList = function(node) { 31 | while (node) { 32 | if (!node.f && !node.blob) { 33 | node.m = Math.round; 34 | } 35 | node = node._next; 36 | } 37 | }, 38 | p = RoundPropsPlugin.prototype; 39 | 40 | p._onInitAllProps = function() { 41 | var tween = this._tween, 42 | rp = (tween.vars.roundProps.join) ? tween.vars.roundProps : tween.vars.roundProps.split(","), 43 | i = rp.length, 44 | lookup = {}, 45 | rpt = tween._propLookup.roundProps, 46 | prop, pt, next; 47 | while (--i > -1) { 48 | lookup[rp[i]] = Math.round; 49 | } 50 | i = rp.length; 51 | while (--i > -1) { 52 | prop = rp[i]; 53 | pt = tween._firstPT; 54 | while (pt) { 55 | next = pt._next; //record here, because it may get removed 56 | if (pt.pg) { 57 | pt.t._mod(lookup); 58 | } else if (pt.n === prop) { 59 | if (pt.f === 2 && pt.t) { //a blob (text containing multiple numeric values) 60 | _roundLinkedList(pt.t._firstPT); 61 | } else { 62 | this._add(pt.t, prop, pt.s, pt.c); 63 | //remove from linked list 64 | if (next) { 65 | next._prev = pt._prev; 66 | } 67 | if (pt._prev) { 68 | pt._prev._next = next; 69 | } else if (tween._firstPT === pt) { 70 | tween._firstPT = next; 71 | } 72 | pt._next = pt._prev = null; 73 | tween._propLookup[prop] = rpt; 74 | } 75 | } 76 | pt = next; 77 | } 78 | } 79 | return false; 80 | }; 81 | 82 | p._add = function(target, p, s, c) { 83 | this._addTween(target, p, s, s + c, p, Math.round); 84 | this._overwriteProps.push(p); 85 | }; 86 | 87 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } -------------------------------------------------------------------------------- /src/css/onstroke.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | .wrapper { 7 | position: relative; 8 | max-width: 540px; 9 | margin: 0 auto; 10 | height: 100vh; 11 | overflow: hidden; 12 | } 13 | 14 | .wrapper canvas { 15 | display: block; 16 | width: 100%; 17 | height: auto; 18 | } 19 | 20 | .levels { 21 | position: absolute; 22 | width: 100%; 23 | height: 100vh; 24 | top: 0; 25 | left: 0; 26 | list-style: none; 27 | padding: 0; 28 | margin: 0; 29 | overflow-x: hidden; 30 | overflow-y: auto; 31 | -webkit-overflow-scrolling:touch; 32 | transition: transform .6s ease; 33 | } 34 | 35 | .level { 36 | position: relative; 37 | width: 100%; 38 | height: 60px; 39 | box-sizing: border-box; 40 | padding: 20px 10px 0; 41 | display: flex; 42 | align-items: center; 43 | box-align: center; 44 | } 45 | 46 | .level::after { 47 | content: ''; 48 | position: absolute; 49 | right: 0; 50 | bottom: 0; 51 | left: 70px; 52 | height: 1px; 53 | background-color: #ccc; 54 | } 55 | 56 | .level-no { 57 | width: 50px; 58 | height: 40px; 59 | line-height: 40px; 60 | text-align: center; 61 | color: #fff; 62 | background-color: #e11222; 63 | } 64 | 65 | .level-arrow { 66 | width: 10px; 67 | height: 10px; 68 | border-width: 2px 2px 0 0; 69 | border-style: solid; 70 | border-color: #ccc; 71 | margin: 0 10px; 72 | transform: rotate(45deg); 73 | } 74 | 75 | .level-name { 76 | flex: 1; 77 | margin-left: 10px; 78 | } 79 | 80 | .game { 81 | position: absolute; 82 | top: 0; 83 | left: 100%; 84 | width: 100%; 85 | height: 100vh; 86 | overflow: hidden; 87 | transition: transform .6s ease; 88 | } 89 | 90 | .game-back { 91 | position: absolute; 92 | z-index: 1; 93 | top: 0; 94 | left: 0; 95 | width: 100%; 96 | height: 40px; 97 | line-height: 40px; 98 | text-align: center; 99 | background-color: #efefef; 100 | box-shadow: 0 0px 10px rgba(0, 0, 0, .2); 101 | } 102 | 103 | .game-back::before { 104 | content: ''; 105 | position: absolute; 106 | width: 100%; 107 | height: 1px; 108 | background-color: #ccc; 109 | bottom: 0; 110 | left: 0; 111 | } 112 | 113 | .game-back::after { 114 | content: ''; 115 | position: absolute; 116 | width: 10px; 117 | height: 10px; 118 | border-width: 2px 0 0 2px; 119 | border-style: solid; 120 | border-color: #ccc; 121 | top: 12px; 122 | left: 10px; 123 | transform: rotate(315deg); 124 | } 125 | 126 | .easel { 127 | position: relative; 128 | width: 100%; 129 | } 130 | .game-control { 131 | position: absolute; 132 | z-index: 1; 133 | bottom: 0; 134 | left: 0; 135 | width: 100%; 136 | height: 40px; 137 | line-height: 40px; 138 | text-align: center; 139 | background-color: #efefef; 140 | box-shadow: 0 0px 10px rgba(0, 0, 0, .2); 141 | display: flex; 142 | } 143 | 144 | .game-control-reset, .game-control-rollback { 145 | flex: 1; 146 | } 147 | 148 | .game-control-rollback 149 | { 150 | color: #e11222; 151 | } 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /src/plugin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OneStrokePlugin 5 | 45 | 46 | 47 |
48 | 49 |
50 |
+
51 |
-
52 | 53 | 54 | 55 | 56 | 118 | -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/DirectionalRotationPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 0.3.1 3 | * DATE: 2017-06-19 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | **/ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | _gsScope._gsDefine.plugin({ 18 | propName: "directionalRotation", 19 | version: "0.3.1", 20 | API: 2, 21 | 22 | //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 23 | init: function(target, value, tween, index) { 24 | if (typeof(value) !== "object") { 25 | value = {rotation:value}; 26 | } 27 | this.finals = {}; 28 | var cap = (value.useRadians === true) ? Math.PI * 2 : 360, 29 | min = 0.000001, 30 | p, v, start, end, dif, split; 31 | for (p in value) { 32 | if (p !== "useRadians") { 33 | end = value[p]; 34 | if (typeof(end) === "function") { 35 | end = end(index, target); 36 | } 37 | split = (end + "").split("_"); 38 | v = split[0]; 39 | start = parseFloat( (typeof(target[p]) !== "function") ? target[p] : target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ]() ); 40 | end = this.finals[p] = (typeof(v) === "string" && v.charAt(1) === "=") ? start + parseInt(v.charAt(0) + "1", 10) * Number(v.substr(2)) : Number(v) || 0; 41 | dif = end - start; 42 | if (split.length) { 43 | v = split.join("_"); 44 | if (v.indexOf("short") !== -1) { 45 | dif = dif % cap; 46 | if (dif !== dif % (cap / 2)) { 47 | dif = (dif < 0) ? dif + cap : dif - cap; 48 | } 49 | } 50 | if (v.indexOf("_cw") !== -1 && dif < 0) { 51 | dif = ((dif + cap * 9999999999) % cap) - ((dif / cap) | 0) * cap; 52 | } else if (v.indexOf("ccw") !== -1 && dif > 0) { 53 | dif = ((dif - cap * 9999999999) % cap) - ((dif / cap) | 0) * cap; 54 | } 55 | } 56 | if (dif > min || dif < -min) { 57 | this._addTween(target, p, start, start + dif, p); 58 | this._overwriteProps.push(p); 59 | } 60 | } 61 | } 62 | return true; 63 | }, 64 | 65 | //called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.) 66 | set: function(ratio) { 67 | var pt; 68 | if (ratio !== 1) { 69 | this._super.setRatio.call(this, ratio); 70 | } else { 71 | pt = this._firstPT; 72 | while (pt) { 73 | if (pt.f) { 74 | pt.t[pt.p](this.finals[pt.p]); 75 | } else { 76 | pt.t[pt.p] = this.finals[pt.p]; 77 | } 78 | pt = pt._next; 79 | } 80 | } 81 | } 82 | 83 | })._autoCSS = true; 84 | 85 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 86 | 87 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 88 | (function(name) { 89 | "use strict"; 90 | var getGlobal = function() { 91 | return (_gsScope.GreenSockGlobals || _gsScope)[name]; 92 | }; 93 | if (typeof(module) !== "undefined" && module.exports) { //node 94 | require("../TweenLite.js"); 95 | module.exports = getGlobal(); 96 | } else if (typeof(define) === "function" && define.amd) { //AMD 97 | define(["TweenLite"], getGlobal); 98 | } 99 | }("DirectionalRotationPlugin")); -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/CSSRulePlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 0.6.6 3 | * DATE: 2017-06-29 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | */ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | _gsScope._gsDefine("plugins.CSSRulePlugin", ["plugins.TweenPlugin","TweenLite","plugins.CSSPlugin"], function(TweenPlugin, TweenLite, CSSPlugin) { 18 | 19 | /** @constructor **/ 20 | var CSSRulePlugin = function() { 21 | TweenPlugin.call(this, "cssRule"); 22 | this._overwriteProps.length = 0; 23 | }, 24 | _doc = _gsScope.document, 25 | _superSetRatio = CSSPlugin.prototype.setRatio, 26 | p = CSSRulePlugin.prototype = new CSSPlugin(); 27 | 28 | p._propName = "cssRule"; 29 | p.constructor = CSSRulePlugin; 30 | CSSRulePlugin.version = "0.6.6"; 31 | CSSRulePlugin.API = 2; 32 | 33 | /** 34 | * Searches the style sheets in the document for a particular selector like ".myClass" or "a" or "a:hover" or ":after" and 35 | * returns a reference to that style sheet (or an array of them in the case of a pseudo selector like ":after"). Then you 36 | * can animate the individual properties of the style sheet. 37 | * 38 | * @param {!string} selector a string describing the selector, like ".myClass" or "a" or "a:hover" or ":after" 39 | * @return a reference to the style sheet (or an array of them in the case of a pseudo selector). If none was found, null is returned (or an empty array for a pseudo selector) 40 | */ 41 | CSSRulePlugin.getRule = function(selector) { 42 | var ruleProp = _doc.all ? 'rules' : 'cssRules', 43 | ss = _doc.styleSheets, 44 | i = ss.length, 45 | pseudo = (selector.charAt(0) === ":"), 46 | j, curSS, cs, a; 47 | selector = (pseudo ? "" : ",") + selector.split("::").join(":").toLowerCase() + ","; //note: old versions of IE report tag name selectors as upper case, so we just change everything to lowercase. 48 | if (pseudo) { 49 | a = []; 50 | } 51 | while (--i > -1) { 52 | //Firefox may throw insecure operation errors when css is loaded from other domains, so try/catch. 53 | try { 54 | curSS = ss[i][ruleProp]; 55 | if (!curSS) { 56 | continue; 57 | } 58 | j = curSS.length; 59 | } catch (e) { 60 | console.log(e); 61 | continue; 62 | } 63 | while (--j > -1) { 64 | cs = curSS[j]; 65 | if (cs.selectorText && ("," + cs.selectorText.split("::").join(":").toLowerCase() + ",").indexOf(selector) !== -1) { //note: IE adds an extra ":" to pseudo selectors, so .myClass:after becomes .myClass::after, so we need to strip the extra one out. 66 | if (pseudo) { 67 | a.push(cs.style); 68 | } else { 69 | return cs.style; 70 | } 71 | } 72 | } 73 | } 74 | return a; 75 | }; 76 | 77 | 78 | // @private gets called when the tween renders for the first time. This kicks everything off, recording start/end values, etc. 79 | p._onInitTween = function(target, value, tween) { 80 | if (target.cssText === undefined) { 81 | return false; 82 | } 83 | var div = target._gsProxy = target._gsProxy || _doc.createElement("div"); 84 | this._ss = target; 85 | this._proxy = div.style; 86 | div.style.cssText = target.cssText; 87 | CSSPlugin.prototype._onInitTween.call(this, div, value, tween); //we just offload all the work to the regular CSSPlugin and then copy the cssText back over to the rule in the setRatio() method. This allows us to have all of the updates to CSSPlugin automatically flow through to CSSRulePlugin instead of having to maintain both 88 | return true; 89 | }; 90 | 91 | 92 | 93 | // @private gets called every time the tween updates, passing the new ratio (typically a value between 0 and 1, but not always (for example, if an Elastic.easeOut is used, the value can jump above 1 mid-tween). It will always start and 0 and end at 1. 94 | p.setRatio = function(v) { 95 | _superSetRatio.call(this, v); 96 | this._ss.cssText = this._proxy.cssText; 97 | }; 98 | 99 | 100 | TweenPlugin.activate([CSSRulePlugin]); 101 | return CSSRulePlugin; 102 | 103 | }, true); 104 | 105 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 106 | 107 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 108 | (function(name) { 109 | "use strict"; 110 | var getGlobal = function() { 111 | return (_gsScope.GreenSockGlobals || _gsScope)[name]; 112 | }; 113 | if (typeof(module) !== "undefined" && module.exports) { //node 114 | require("../TweenLite.js"); 115 | module.exports = getGlobal(); 116 | } else if (typeof(define) === "function" && define.amd) { //AMD 117 | define(["TweenLite"], getGlobal); 118 | } 119 | }("CSSRulePlugin")); -------------------------------------------------------------------------------- /src/onestroke.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | H5小游戏100例: 一笔画 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 20 |
21 |
22 | 23 |
24 |
重新开始
25 |
回退
26 |
27 |
28 |
29 | 30 | 31 | 32 | 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/TextPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 0.6.1 3 | * DATE: 2017-06-30 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | */ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | var _getText = function(e) { 18 | var type = e.nodeType, 19 | result = ""; 20 | if (type === 1 || type === 9 || type === 11) { 21 | if (typeof(e.textContent) === "string") { 22 | return e.textContent; 23 | } else { 24 | for ( e = e.firstChild; e; e = e.nextSibling ) { 25 | result += _getText(e); 26 | } 27 | } 28 | } else if (type === 3 || type === 4) { 29 | return e.nodeValue; 30 | } 31 | return result; 32 | }, 33 | _emojiStart = 0xD800, 34 | _emojiEnd = 0xDBFF, 35 | _emojiLowStart = 0xDC00, 36 | _emojiRegionStart = 0x1F1E6, 37 | _emojiRegionEnd = 0x1F1FF, 38 | _emojiModStart = 0x1f3fb, 39 | _emojiModEnd = 0x1f3ff, 40 | _emojiPairCode = function(s) { 41 | return ((s.charCodeAt(0) - _emojiStart) << 10) + (s.charCodeAt(1) - _emojiLowStart) + 0x10000; 42 | }, 43 | _emojiSafeSplit = function(text, delimiter) { //like calling String.split(delimiter) except that it keeps emoji characters together. 44 | if (delimiter !== "") { 45 | return text.split(delimiter); 46 | } 47 | var l = text.length, 48 | a = [], 49 | character, i, emojiPair1, emojiPair2, j; 50 | for (i = 0; i < l; i++) { 51 | character = text.charAt(i); 52 | if ((character.charCodeAt(0) >= _emojiStart && character.charCodeAt(0) <= _emojiEnd) || (text.charCodeAt(i+1) >= 0xFE00 && text.charCodeAt(i+1) <= 0xFE0F)) { //special emoji characters use 2 or 4 unicode characters that we must keep together. 53 | emojiPair1 = _emojiPairCode(text.substr(i, 2)); 54 | emojiPair2 = _emojiPairCode(text.substr(i + 2, 2)); 55 | j = ((emojiPair1 >= _emojiRegionStart && emojiPair1 <= _emojiRegionEnd && emojiPair2 >= _emojiRegionStart && emojiPair2 <= _emojiRegionEnd) || (emojiPair2 >= _emojiModStart && emojiPair2 <= _emojiModEnd)) ? 4 : 2; 56 | a.push(text.substr(i, j)); 57 | i += j - 1; 58 | } else { 59 | a.push(character); 60 | } 61 | } 62 | return a; 63 | }, 64 | TextPlugin = _gsScope._gsDefine.plugin({ 65 | propName: "text", 66 | API: 2, 67 | version:"0.6.1", 68 | 69 | //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 70 | init: function(target, value, tween, index) { 71 | var i = target.nodeName.toUpperCase(), 72 | shrt; 73 | if (typeof(value) === "function") { 74 | value = value(index, target); 75 | } 76 | this._svg = (target.getBBox && (i === "TEXT" || i === "TSPAN")); 77 | if (!("innerHTML" in target) && !this._svg) { 78 | return false; 79 | } 80 | this._target = target; 81 | if (typeof(value) !== "object") { 82 | value = {value:value}; 83 | } 84 | if (value.value === undefined) { 85 | this._text = this._original = [""]; 86 | return true; 87 | } 88 | this._delimiter = value.delimiter || ""; 89 | this._original = _emojiSafeSplit(_getText(target).replace(/\s+/g, " "), this._delimiter); 90 | this._text = _emojiSafeSplit(value.value.replace(/\s+/g, " "), this._delimiter); 91 | this._runBackwards = (tween.vars.runBackwards === true); 92 | if (this._runBackwards) { 93 | i = this._original; 94 | this._original = this._text; 95 | this._text = i; 96 | } 97 | if (typeof(value.newClass) === "string") { 98 | this._newClass = value.newClass; 99 | this._hasClass = true; 100 | } 101 | if (typeof(value.oldClass) === "string") { 102 | this._oldClass = value.oldClass; 103 | this._hasClass = true; 104 | } 105 | i = this._original.length - this._text.length; 106 | shrt = (i < 0) ? this._original : this._text; 107 | this._fillChar = value.fillChar || (value.padSpace ? " " : ""); 108 | if (i < 0) { 109 | i = -i; 110 | } 111 | while (--i > -1) { 112 | shrt.push(this._fillChar); 113 | } 114 | return true; 115 | }, 116 | 117 | //called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.) 118 | set: function(ratio) { 119 | if (ratio > 1) { 120 | ratio = 1; 121 | } else if (ratio < 0) { 122 | ratio = 0; 123 | } 124 | if (this._runBackwards) { 125 | ratio = 1 - ratio; 126 | } 127 | var l = this._text.length, 128 | i = (ratio * l + 0.5) | 0, 129 | applyNew, applyOld, str; 130 | if (this._hasClass) { 131 | applyNew = (this._newClass && i !== 0); 132 | applyOld = (this._oldClass && i !== l); 133 | str = (applyNew ? "" : "") + this._text.slice(0, i).join(this._delimiter) + (applyNew ? "" : "") + (applyOld ? "" : "") + this._delimiter + this._original.slice(i).join(this._delimiter) + (applyOld ? "" : ""); 134 | } else { 135 | str = this._text.slice(0, i).join(this._delimiter) + this._delimiter + this._original.slice(i).join(this._delimiter); 136 | } 137 | if (this._svg) { //SVG text elements don't have an "innerHTML" in Microsoft browsers. 138 | this._target.textContent = str; 139 | } else { 140 | this._target.innerHTML = (this._fillChar === " " && str.indexOf(" ") !== -1) ? str.split(" ").join("  ") : str; 141 | } 142 | } 143 | 144 | }), 145 | p = TextPlugin.prototype; 146 | 147 | p._newClass = p._oldClass = p._delimiter = ""; 148 | 149 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 150 | 151 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 152 | (function(name) { 153 | "use strict"; 154 | var getGlobal = function() { 155 | return (_gsScope.GreenSockGlobals || _gsScope)[name]; 156 | }; 157 | if (typeof(module) !== "undefined" && module.exports) { //node 158 | require("../TweenLite.js"); 159 | module.exports = getGlobal(); 160 | } else if (typeof(define) === "function" && define.amd) { //AMD 161 | define(["TweenLite"], getGlobal); 162 | } 163 | }("TextPlugin")); -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/ModifiersPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 0.0.3 3 | * DATE: 2017-06-19 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | */ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | var _cssRatioSetter = function(pt, cssp, mod) { //Takes an individual CSSPropTween and converts it into a type:2 that has a setRatio that does everything the regular CSSPlugin.setRatio() method does but applying the mod() too. We do this to keep the main CSSPlugin.setRatio() as fast as possible (the vast majority of times, no mod() will be necessary) 18 | var type = pt.type, 19 | oldSetRatio = pt.setRatio, 20 | tween = cssp._tween, 21 | target = cssp._target; 22 | pt.type = 2; 23 | pt.m = mod; 24 | pt.setRatio = function(v) { 25 | var min = 0.000001, 26 | val, str, i; 27 | if (v === 1 && (tween._time === tween._duration || tween._time === 0)) { 28 | 29 | if (type !== 2) { 30 | if (pt.r && type !== -1) { 31 | val = Math.round(pt.s + pt.c); 32 | if (!type) { 33 | pt.t[pt.p] = mod(val + pt.xs0, target); 34 | } else if (type === 1) { 35 | str = pt.xs0 + val + pt.xs1; 36 | for (i = 1; i < pt.l; i++) { 37 | str += pt["xn"+i] + pt["xs"+(i+1)]; 38 | } 39 | pt.t[pt.p] = mod(str, target); 40 | } 41 | } else { 42 | pt.t[pt.p] = mod(pt.e, target); 43 | } 44 | } else { 45 | oldSetRatio.call(pt, v); 46 | } 47 | 48 | } else if (v || !(tween._time === tween._duration || tween._time === 0) || tween._rawPrevTime === -0.000001) { 49 | val = pt.c * v + pt.s; 50 | if (pt.r) { 51 | val = Math.round(val); 52 | } else if (val < min) if (val > -min) { 53 | val = 0; 54 | } 55 | if (!type) { 56 | pt.t[pt.p] = mod(val + pt.xs0, target); 57 | } else if (type === 1) { 58 | str = pt.xs0 + val + pt.xs1; 59 | for (i = 1; i < pt.l; i++) { 60 | str += pt["xn"+i] + pt["xs"+(i+1)]; 61 | } 62 | pt.t[pt.p] = mod(str, target); 63 | 64 | } else if (type === -1) { //non-tweening value 65 | pt.t[pt.p] = mod(pt.xs0, target); 66 | 67 | } else if (oldSetRatio) { 68 | oldSetRatio.call(pt, v); 69 | } 70 | 71 | } else { 72 | if (type !== 2) { 73 | pt.t[pt.p] = mod(pt.b, target); 74 | } else { 75 | oldSetRatio.call(pt, v); 76 | } 77 | } 78 | }; 79 | }, 80 | _modCSS = function(lookup, cssp) { 81 | var pt = cssp._firstPT, 82 | hasBezier = (lookup.rotation && cssp._overwriteProps.join("").indexOf("bezier") !== -1); //when a Bezier tween is applying autoRotation, it's a very special case we need to handle differently. 83 | while (pt) { 84 | if (typeof(lookup[pt.p]) === "function") { 85 | _cssRatioSetter(pt, cssp, lookup[pt.p]); 86 | } else if (hasBezier && pt.n === "bezier" && pt.plugin._overwriteProps.join("").indexOf("rotation") !== -1) { 87 | pt.data.mod = lookup.rotation; 88 | } 89 | pt = pt._next; 90 | } 91 | }, 92 | 93 | ModifiersPlugin = _gsScope._gsDefine.plugin({ 94 | propName: "modifiers", 95 | version: "0.0.3", 96 | API: 2, 97 | 98 | //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 99 | init: function(target, value, tween) { 100 | this._tween = tween; 101 | this._vars = value; 102 | return true; 103 | }, 104 | 105 | initAll: function() { 106 | var tween = this._tween, 107 | lookup = this._vars, 108 | mpt = this, 109 | pt = tween._firstPT, 110 | val, next; 111 | while (pt) { 112 | next = pt._next; //record here, because it may get removed 113 | val = lookup[pt.n]; 114 | if (pt.pg) { 115 | if (pt.t._propName === "css") { //handle CSSPlugin uniquely (for performance, due to the fact that the values almost always are a concatenation of numbers and strings, like suffixes, and we don't want to slow down the regular CSSPlugin setRatio() performance with conditional checks for if the value needs to be modded, so we pull any modding prop out and change it to a type:2 one that simply calls a setRatio() method where we encapsulate the modding and update all together. That way, it says in the main CSSProp linked list and just has some custom logic applied to it inside its setRatio()) 116 | _modCSS(lookup, pt.t); 117 | } else if (pt.t !== mpt) { //don't run modProps on modProps :) 118 | val = lookup[pt.t._propName]; 119 | pt.t._mod((typeof(val) === "object") ? val : lookup); 120 | } 121 | } else if (typeof(val) === "function") { 122 | if (pt.f === 2 && pt.t) { //a blob (text containing multiple numeric values) 123 | pt.t._applyPT.m = val; 124 | } else { 125 | this._add(pt.t, pt.p, pt.s, pt.c, val); 126 | //remove from linked list 127 | if (next) { 128 | next._prev = pt._prev; 129 | } 130 | if (pt._prev) { 131 | pt._prev._next = next; 132 | } else if (tween._firstPT === pt) { 133 | tween._firstPT = next; 134 | } 135 | pt._next = pt._prev = null; 136 | tween._propLookup[pt.n] = mpt; 137 | } 138 | } 139 | pt = next; 140 | } 141 | return false; 142 | } 143 | 144 | }), 145 | p = ModifiersPlugin.prototype; 146 | 147 | p._add = function(target, p, s, c, mod) { 148 | this._addTween(target, p, s, s + c, p, mod); 149 | this._overwriteProps.push(p); 150 | }; 151 | 152 | p = _gsScope._gsDefine.globals.TweenLite.version.split("."); 153 | if (Number(p[0]) <= 1 && Number(p[1]) < 19 && _gsScope.console) { 154 | console.log("ModifiersPlugin requires GSAP 1.19.0 or later."); 155 | } 156 | 157 | 158 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 159 | 160 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 161 | (function(name) { 162 | "use strict"; 163 | var getGlobal = function() { 164 | return (_gsScope.GreenSockGlobals || _gsScope)[name]; 165 | }; 166 | if (typeof(module) !== "undefined" && module.exports) { //node 167 | require("../TweenLite.js"); 168 | module.exports = getGlobal(); 169 | } else if (typeof(define) === "function" && define.amd) { //AMD 170 | define(["TweenLite"], getGlobal); 171 | } 172 | }("ModifiersPlugin")); -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/TEMPLATE_Plugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 1.2.0 3 | * DATE: 2017-01-17 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * This file is to be used as a simple template for writing your own plugin. See the 7 | * TweenPlugin docs for more details. 8 | * 9 | * You can start by doing a search for "yourCustomProperty" and replace it with whatever the name 10 | * of your property is. This way of defining a plugin was introduced in version 1.9.0 - previous versions 11 | * of TweenLite won't work with this. 12 | * 13 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 14 | * This work is subject to the terms at http://greensock.com/standard-license or for 15 | * Club GreenSock members, the software agreement that was issued with your membership. 16 | * 17 | * @author: Jack Doyle, jack@greensock.com 18 | **/ 19 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 20 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 21 | //ignore the line above this and at the very end - those are for ensuring things load in the proper order 22 | "use strict"; 23 | 24 | _gsScope._gsDefine.plugin({ 25 | propName: "yourCustomProperty", //the name of the property that will get intercepted and handled by this plugin (obviously change it to whatever you want, typically it is camelCase starting with lowercase). 26 | priority: 0, //the priority in the rendering pipeline (0 by default). A priority of -1 would mean this plugin will run after all those with 0 or greater. A priority of 1 would get run before 0, etc. This only matters when a plugin relies on other plugins finishing their work before it runs (or visa-versa) 27 | API: 2, //the API should stay 2 - it just gives us a way to know the method/property structure so that if in the future we change to a different TweenPlugin architecture, we can identify this plugin's structure. 28 | version: "1.0.0", //your plugin's version number 29 | overwriteProps: ["yourCustomProperty"], //an array of property names whose tweens should be overwritten by this plugin. For example, if you create a "scale" plugin that handles both "scaleX" and "scaleY", the overwriteProps would be ["scaleX","scaleY"] so that if there's a scaleX or scaleY tween in-progress when a new "scale" tween starts (using this plugin), it would overwrite the scaleX or scaleY tween. 30 | 31 | /* 32 | * The init function is called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. It receives 3 parameters: 33 | * 1) target [object] - the target of the tween. In cases where the tween's original target is an array (or jQuery object), this target will be the individual object inside that array (a new plugin instance is created for each target in the array). For example, TweenLite.to([obj1, obj2, obj3], 1, {x:100}) the target will be obj1 or obj2 or obj3 rather than the array containing them. 34 | * 2) value [*] - whatever value is passed as the special property value. For example, TweenLite.to(element, 1, {yourCustomProperty:3}) the value would be 3. Or for TweenLite.to(element, 1, {yourCustomProperty:{subProp1:3, subProp2:"whatever"}});, value would be {subProp1:3, subProp2:"whatever"}. 35 | * 3) tween [TweenLite] - the TweenLite (or TweenMax) instance that is managing this plugin instance. This can be useful if you need to check certain state-related properties on the tween (maybe in the set method) like its duration or time. Most of the time, however, you don't need to do anything with the tween. It is provided just in case you want to reference it. 36 | * 4) index [integer] - the index number of the target in the tween. For example, if an array is passed in as the target (or selector text), this would be 0 for the first one, 1 for the second, 2 for the third, etc. This was introduced in GSAP 1.19.0 37 | * 38 | * This function should return true unless you want to have TweenLite/Max skip the plugin altogether and instead treat the property/value like a normal tween (as if the plugin wasn't activated). This is rarely useful, so you should almost always return true. 39 | */ 40 | init: function(target, value, tween, index) { 41 | this._target = target; //we record the target so that we can refer to it in the set method when doing updates. 42 | 43 | /* Next, we create a property tween for "scaleX" and "scaleY" properties of our target 44 | * (we're just using them as a examples of how to set up a property tween with a name, start, and end value). 45 | * the _addTween() method accepts the following parameters: 46 | * 1) target [object] - target object whose property this tween will control. 47 | * 2) property [string] - the name of the property, like "scaleX" or "scaleY" 48 | * 3) start [number] - The starting value of the property. For example, if you're tweening from 0 to 100, start would be 0. 49 | * 4) end [number] - the ending value of the property. For example, if you're tweening from 0 to 100, end would be 100. 50 | * 5) overwriteProperty [string] - the name that gets registered as the overwrite property so that if another concurrent tween of the same target gets created and it is tweening a property with this name, this one will be overwritten. Typically this is the same as "property". 51 | * 6) round [boolean] - if true, the updated value on each update will be rounded to the nearest integer. [false by default] 52 | * You do NOT need to use _addTween() at all. It is merely a convenience. You can record your own values internally or whatever you want. 53 | */ 54 | this._addTween(target, "scaleX", target.scaleX, value, "scaleX", false); 55 | this._addTween(target, "scaleY", target.scaleY, value, "scaleY", false); 56 | 57 | //now, just for kicks, we'll record the starting "alpha" value and amount of change so that we can manage this manually rather than _addTween() (again, totally fictitious, just for an example) 58 | this._alphaStart = target.alpha; 59 | this._alphaChange = value.alpha - target.alpha; 60 | 61 | //always return true unless we want to scrap the plugin and have the value treated as a normal property tween (very uncommon) 62 | return true; 63 | }, 64 | 65 | //[optional] - called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.). If you're using this._super._addTween() for all your tweens and you don't need to do anything special on each frame besides updating those values, you can omit this "set" function altogether. 66 | set: function(ratio) { 67 | //since we used _addTween() inside init function, it created some property tweens that we'll update by calling the parent prototype's setRatio() (otherwise, the property tweens wouldn't get their values updated). this._super refers to the TweenPlugin prototype from which the plugin inherits (not that you need to worry about that). 68 | this._super.setRatio.call(this, ratio); 69 | 70 | //now manually set the alpha 71 | this._target.alpha = this._alphaStart + this._alphaChange * ratio; 72 | } 73 | 74 | }); 75 | 76 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/ScrollToPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 1.9.0 3 | * DATE: 2017-06-19 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | **/ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | var _doc = (_gsScope.document || {}).documentElement, 18 | _window = _gsScope, 19 | _max = function(element, axis) { 20 | var dim = (axis === "x") ? "Width" : "Height", 21 | scroll = "scroll" + dim, 22 | client = "client" + dim, 23 | body = document.body; 24 | return (element === _window || element === _doc || element === body) ? Math.max(_doc[scroll], body[scroll]) - (_window["inner" + dim] || _doc[client] || body[client]) : element[scroll] - element["offset" + dim]; 25 | }, 26 | _unwrapElement = function(value) { 27 | if (typeof(value) === "string") { 28 | value = TweenLite.selector(value); 29 | } 30 | if (value.length && value !== _window && value[0] && value[0].style && !value.nodeType) { 31 | value = value[0]; 32 | } 33 | return (value === _window || (value.nodeType && value.style)) ? value : null; 34 | }, 35 | _buildGetter = function(e, axis) { //pass in an element and an axis ("x" or "y") and it'll return a getter function for the scroll position of that element (like scrollTop or scrollLeft, although if the element is the window, it'll use the pageXOffset/pageYOffset or the documentElement's scrollTop/scrollLeft or document.body's. Basically this streamlines things and makes a very fast getter across browsers. 36 | var p = "scroll" + ((axis === "x") ? "Left" : "Top"); 37 | if (e === _window) { 38 | if (e.pageXOffset != null) { 39 | p = "page" + axis.toUpperCase() + "Offset"; 40 | } else if (_doc[p] != null) { 41 | e = _doc; 42 | } else { 43 | e = document.body; 44 | } 45 | } 46 | return function() { 47 | return e[p]; 48 | }; 49 | }, 50 | _getOffset = function(element, container) { 51 | var rect = _unwrapElement(element).getBoundingClientRect(), 52 | isRoot = (!container || container === _window || container === document.body), 53 | cRect = (isRoot ? _doc : container).getBoundingClientRect(), 54 | offsets = {x: rect.left - cRect.left, y: rect.top - cRect.top}; 55 | if (!isRoot && container) { //only add the current scroll position if it's not the window/body. 56 | offsets.x += _buildGetter(container, "x")(); 57 | offsets.y += _buildGetter(container, "y")(); 58 | } 59 | return offsets; 60 | }, 61 | _parseVal = function(value, target, axis) { 62 | var type = typeof(value); 63 | return !isNaN(value) ? parseFloat(value) : (type === "number" || (type === "string" && value.charAt(1) === "=")) ? value : (value === "max") ? _max(target, axis) : Math.min(_max(target, axis), _getOffset(value, target)[axis]); 64 | }, 65 | 66 | ScrollToPlugin = _gsScope._gsDefine.plugin({ 67 | propName: "scrollTo", 68 | API: 2, 69 | global: true, 70 | version:"1.9.0", 71 | 72 | //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 73 | init: function(target, value, tween) { 74 | this._wdw = (target === _window); 75 | this._target = target; 76 | this._tween = tween; 77 | if (typeof(value) !== "object") { 78 | value = {y:value}; //if we don't receive an object as the parameter, assume the user intends "y". 79 | if (typeof(value.y) === "string" && value.y !== "max" && value.y.charAt(1) !== "=") { 80 | value.x = value.y; 81 | } 82 | } else if (value.nodeType) { 83 | value = {y:value, x:value}; 84 | } 85 | this.vars = value; 86 | this._autoKill = (value.autoKill !== false); 87 | this.getX = _buildGetter(target, "x"); 88 | this.getY = _buildGetter(target, "y"); 89 | this.x = this.xPrev = this.getX(); 90 | this.y = this.yPrev = this.getY(); 91 | if (value.x != null) { 92 | this._addTween(this, "x", this.x, _parseVal(value.x, target, "x") - (value.offsetX || 0), "scrollTo_x", true); 93 | this._overwriteProps.push("scrollTo_x"); 94 | } else { 95 | this.skipX = true; 96 | } 97 | if (value.y != null) { 98 | this._addTween(this, "y", this.y, _parseVal(value.y, target, "y") - (value.offsetY || 0), "scrollTo_y", true); 99 | this._overwriteProps.push("scrollTo_y"); 100 | } else { 101 | this.skipY = true; 102 | } 103 | return true; 104 | }, 105 | 106 | //called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.) 107 | set: function(v) { 108 | this._super.setRatio.call(this, v); 109 | 110 | var x = (this._wdw || !this.skipX) ? this.getX() : this.xPrev, 111 | y = (this._wdw || !this.skipY) ? this.getY() : this.yPrev, 112 | yDif = y - this.yPrev, 113 | xDif = x - this.xPrev, 114 | threshold = ScrollToPlugin.autoKillThreshold; 115 | 116 | if (this.x < 0) { //can't scroll to a position less than 0! Might happen if someone uses a Back.easeOut or Elastic.easeOut when scrolling back to the top of the page (for example) 117 | this.x = 0; 118 | } 119 | if (this.y < 0) { 120 | this.y = 0; 121 | } 122 | if (this._autoKill) { 123 | //note: iOS has a bug that throws off the scroll by several pixels, so we need to check if it's within 7 pixels of the previous one that we set instead of just looking for an exact match. 124 | if (!this.skipX && (xDif > threshold || xDif < -threshold) && x < _max(this._target, "x")) { 125 | this.skipX = true; //if the user scrolls separately, we should stop tweening! 126 | } 127 | if (!this.skipY && (yDif > threshold || yDif < -threshold) && y < _max(this._target, "y")) { 128 | this.skipY = true; //if the user scrolls separately, we should stop tweening! 129 | } 130 | if (this.skipX && this.skipY) { 131 | this._tween.kill(); 132 | if (this.vars.onAutoKill) { 133 | this.vars.onAutoKill.apply(this.vars.onAutoKillScope || this._tween, this.vars.onAutoKillParams || []); 134 | } 135 | } 136 | } 137 | if (this._wdw) { 138 | _window.scrollTo((!this.skipX) ? this.x : x, (!this.skipY) ? this.y : y); 139 | } else { 140 | if (!this.skipY) { 141 | this._target.scrollTop = this.y; 142 | } 143 | if (!this.skipX) { 144 | this._target.scrollLeft = this.x; 145 | } 146 | } 147 | this.xPrev = this.x; 148 | this.yPrev = this.y; 149 | } 150 | 151 | }), 152 | p = ScrollToPlugin.prototype; 153 | 154 | ScrollToPlugin.max = _max; 155 | ScrollToPlugin.getOffset = _getOffset; 156 | ScrollToPlugin.buildGetter = _buildGetter; 157 | ScrollToPlugin.autoKillThreshold = 7; 158 | 159 | p._kill = function(lookup) { 160 | if (lookup.scrollTo_x) { 161 | this.skipX = true; 162 | } 163 | if (lookup.scrollTo_y) { 164 | this.skipY = true; 165 | } 166 | return this._super._kill.call(this, lookup); 167 | }; 168 | 169 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 170 | 171 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 172 | (function(name) { 173 | "use strict"; 174 | var getGlobal = function() { 175 | return (_gsScope.GreenSockGlobals || _gsScope)[name]; 176 | }; 177 | if (typeof(module) !== "undefined" && module.exports) { //node 178 | require("../TweenLite.js"); 179 | module.exports = getGlobal(); 180 | } else if (typeof(define) === "function" && define.amd) { //AMD 181 | define(["TweenLite"], getGlobal); 182 | } 183 | }("ScrollToPlugin")); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # H5小游戏100例:一笔画 2 | 3 | 一笔画是图论[科普](https://zh.wikipedia.org/wiki/%E5%9B%BE%E8%AE%BA)中一个著名的问题,它起源于柯尼斯堡七桥问题[科普](https://zh.wikipedia.org/wiki/%E6%9F%AF%E5%B0%BC%E6%96%AF%E5%A0%A1%E4%B8%83%E6%A1%A5%E9%97%AE%E9%A2%98)。数学家欧拉在他1736年发表的论文《柯尼斯堡的七桥》中不仅解决了七桥问题,也提出了一笔画定理,顺带解决了一笔画问题。用图论的术语来说,对于一个给定的连通图[科普](https://zh.wikipedia.org/wiki/%E8%BF%9E%E9%80%9A%E5%9B%BE)存在一条恰好包含所有线段并且没有重复的路径,这条路径就是「一笔画」。 4 | 5 | 寻找连通图这条路径的过程就是「一笔画」的游戏过程,如下: 6 | 7 | ![一笔画](http://7xv39r.com1.z0.glb.clouddn.com/2017-10-31-demo.gif) 8 | 9 | ## 游戏的实现 10 | 11 | 「一笔画」的实现不复杂,笔者把实现过程分成两步: 12 | 13 | 1. 底图绘制 14 | 2. 交互绘制 15 | 16 | 「底图绘制」把连通图以「点线」的形式显示在画布上,是游戏最容易实现的部分;「交互绘制」是用户绘制解题路径的过程,这个过程会主要是处理点与点动态成线的逻辑。 17 | 18 | ### 底图绘制 19 | 「一笔画」是多关卡的游戏模式,笔者决定把关卡(连通图)的定制以一个配置接口的形式对外暴露。对外暴露关卡接口需要有一套描述连通图形状的规范,而在笔者面前有两个选项: 20 | 21 | - 点记法 22 | - 线记法 23 | 24 | 举个连通图 ------ 五角星为例来说一下这两个选项。 25 | 26 | ![五角星](http://7xv39r.com1.z0.glb.clouddn.com/2017-10-31-five.png?v=2) 27 | 28 | 点记法如下: 29 | ```javascript 30 | levels: [ 31 | // 当前关卡 32 | { 33 | name: "五角星", 34 | coords: [ 35 | {x: Ax, y: Ay}, 36 | {x: Bx, y: By}, 37 | {x: Cx, y: Cy}, 38 | {x: Dx, y: Dy}, 39 | {x: Ex, y: Ey}, 40 | {x: Ax, y: Ay} 41 | ] 42 | } 43 | ... 44 | ] 45 | ``` 46 | 线记法如下: 47 | ```javascript 48 | levels: [ 49 | // 当前关卡 50 | { 51 | name: "五角星", 52 | lines: [ 53 | {x1: Ax, y1: Ay, x2: Bx, y2: By}, 54 | {x1: Bx, y1: By, x2: Cx, y2: Cy}, 55 | {x1: Cx, y1: Cy, x2: Dx, y2: Dy}, 56 | {x1: Dx, y1: Dy, x2: Ex, y2: Ey}, 57 | {x1: Ex, y1: Ey, x2: Ax, y2: Ay} 58 | ] 59 | } 60 | ] 61 | ``` 62 | 63 | 「点记法」记录关卡通关的一个答案,即端点要按一定的顺序存放到数组 `coords`中,它是有序性的记录。「线记法」通过两点描述连通图的线段,它是无序的记录。「点记法」最大的优势是表现更简洁,但它必须记录一个通关答案,笔者只是关卡的搬运工不是关卡创造者,所以笔者最终选择了「线记法」。:) 64 | 65 | ### 交互绘制 66 | 67 | 在画布上绘制路径,从视觉上说是「选择或连接连通图端点」的过程,这个过程需要解决2个问题: 68 | 69 | - 手指下是否有端点 70 | - 选中点到待选中点之间能否成线 71 | 72 | 收集连通图端点的坐标,再监听手指滑过的坐标可以知道「手指下是否有点」。以下伪代码是收集端点坐标: 73 | 74 | ```javascript 75 | // 端点坐标信息 76 | let coords = []; 77 | lines.forEach(({x1, y1, x2, y2}) => { 78 | // (x1, y1) 在 coords 数组不存在 79 | if(!isExist(x1, y1)) coords.push([x1, y1]); 80 | // (x2, y2) 在 coords 数组不存在 81 | if(!isExist(x2, y2)) coords.push([x2, y2]); 82 | }); 83 | ``` 84 | 85 | 以下伪代码是监听手指滑动: 86 | ```javascript 87 | easel.addEventListener("touchmove", e => { 88 | let x0 = e.targetTouches[0].pageX, y0 = e.targetTouches[0].pageY; 89 | // 端点半径 ------ 取连通图端点半径的2倍,提升移动端体验 90 | let r = radius * 2; 91 | for(let [x, y] of coords){ 92 | if(Math.sqrt(Math.pow(x - x0, 2) + Math.pow(y - y0), 2) <= r){ 93 | // 手指下有端点,判断能否连线 94 | if(canConnect(x, y)) { 95 | // todo 96 | } 97 | break; 98 | } 99 | } 100 | }) 101 | ``` 102 | 在未绘制任何线段或端点之前,手指滑过的任意端点都会被视作「一笔画」的起始点;在绘制了线段(或有选中点)后,手指滑过的端点能否与选中点串连成线段需要依据现有条件进行判断。 103 | 104 | ![示意图](http://7xv39r.com1.z0.glb.clouddn.com/2017-10-31-five-2.png?v=2) 105 | 106 | 上图,点A与点B可连接成线段,而点A与点C不能连接。笔者把「可以与指定端点连接成线段的端点称作**有效连接点**」。连通图端点的有效连接点从连通图的线段中提取: 107 | ```javascript 108 | coords.forEach(coord => { 109 | // 有效连接点(坐标)挂载在端点坐标下 110 | coord.validCoords = []; 111 | lines.forEach(({x1, y1, x2, y2}) => { 112 | // 坐标是当前线段的起点 113 | if(coord.x === x1 && coord.y === y1) { 114 | coord.validCoords.push([x2, y2]); 115 | } 116 | // 坐标是当前线段的终点 117 | else if(coord.x === x2 && coord.y === y2) { 118 | coord.validCoords.push([x1, y1]); 119 | } 120 | }) 121 | }) 122 | ``` 123 | But...有效连接点只能判断两个点是否为底图的线段,这只是一个静态的参考,在实际的「交互绘制」中,会遇到以下情况: 124 | 125 | ![AB成线](http://7xv39r.com1.z0.glb.clouddn.com/2017-11-01-five.png) 126 | 127 | 如上图,AB已串连成线段,当前选中点B的有效连接点是 A 与 C。AB 已经连接成线,如果 BA 也串连成线段,那么线段就重复了,所以此时 BA 不能成线,只有 AC 才能成线。 128 | 129 | 对选中点而言,它的有效连接点有两种: 130 | 131 | - 与选中点「成线的有效连接点」 132 | - 与选中点「未成线的有效连接点」 133 | 134 | 其中「未成线的有效连接点」才能参与「交互绘制」,并且它是动态的。 135 | 136 | ![未成线的有效连接点](http://7xv39r.com1.z0.glb.clouddn.com/2017-11-01-valid-vertexes.gif) 137 | 138 | 回头本节内容开头提的两个问题「手指下是否有端点」 与 「选中点到待选中点之间能否成线」,其实可合并为一个问题:**手指下是否存在「未成线的有效连接点」**。只须把监听手指滑动遍历的数组由连通图所有的端点坐标 `coords` 替换为当前选中点的「未成线的有效连接点」即可。 139 | 140 | 至此「一笔画」的主要功能已经实现。可以抢先体验一下: 141 | 142 | ![demo](http://7xv39r.com1.z0.glb.clouddn.com/2017-10-31-qr.png?v=2) 143 | 144 | [https://leeenx.github.io/OneStroke/src/onestroke.html](https://leeenx.github.io/OneStroke/src/onestroke.html) 145 | 146 | 147 | ## 自动识图 148 | 149 | 笔者在录入关卡配置时,发现一个7条边以上的连通图很容易录错或录重线段。笔者在思考能否开发一个自动识别图形的插件,毕竟「一笔画」的图形是有规则的几何图形。 150 | 151 | ![底图](http://7xv39r.com1.z0.glb.clouddn.com/2017-11-02-shape.png) 152 | 153 | 上面的关卡「底图」,一眼就可以识出三个颜色: 154 | 155 | - 白底 156 | - 端点颜色 157 | - 线段颜色 158 | 159 | 并且这三种颜色在「底图」的面积大小顺序是:白底 > 线段颜色 > 端点颜色。底图的「采集色值表算法」很简单,如下伪代码: 160 | 161 | ```javascript 162 | let imageData = ctx.getImageData(); 163 | let data = imageData.data; 164 | // 色值表 165 | let clrs = new Map(); 166 | for(let i = 0, len = data.length; i < len; i += 4) { 167 | let [r, g, b, a] = [data[i], data[i + 1], data[i + 2], data[i + 3]]; 168 | let key = `rgba(${r}, ${g}, ${b}, ${a})`; 169 | let value = clrs.get(key) || {r, g, b, a, count: 0}; 170 | clrs.has(key) ? ++value.count : clrs.set(rgba, {r, g, b, a, count}); 171 | } 172 | ``` 173 | 174 | 对于连通图来说,只要把端点识别出来,连通图的轮廓也就出来了。 175 | 176 | ### 端点识别 177 | 理论上,通过采集的「色值表」可以直接把端点的坐标识别出来。笔者设计的「端点识别算法」分以下2步: 178 | 179 | 1. 按像素扫描底图直到遇到「端点颜色」的像素,进入第二步 180 | 2. 从底图上清除端点并记录它的坐标,返回继续第一步 181 | 182 | 伪代码如下: 183 | ```javascript 184 | for(let i = 0, len = data.length; i < len; i += 4) { 185 | let [r, g, b, a] = [data[i], data[i + 1], data[i + 2], data[i + 3]]; 186 | // 当前像素颜色属于端点 187 | if(isBelongVertex(r, g, b, a)) { 188 | // 在 data 中清空端点 189 | vertex = clearVertex(i); 190 | // 记录端点信息 191 | vertexes.push(vertext); 192 | } 193 | } 194 | ``` 195 | 196 | But... 上面的算法只能跑无损图。笔者在使用了一张手机截屏做测试的时候发现,收集到的「色值表」长度为 5000+ !这直接导致端点和线段的色值无法直接获得。 197 | 198 | 经过分析,可以发现「色值表」里绝大多数色值都是相近的,也就是在原来的「采集色值表算法」的基础上添加一个近似颜色过滤即可以找出端点和线段的主色。伪代码实现如下: 199 | 200 | ```javascript 201 | let lineColor = vertexColor = {count: 0}; 202 | for(let clr of clrs) { 203 | // 与底色相近,跳过 204 | if(isBelongBackground(clr)) continue; 205 | // 线段是数量第二多的颜色,端点是第三多的颜色 206 | if(clr.count > lineColor.count) { 207 | [vertexColor, lineColor] = [lineColor, clr] 208 | } 209 | } 210 | ``` 211 | 212 | 取到端点的主色后,再跑一次「端点识别算法」后居识别出 203 个端点!这是为什么呢? 213 | 214 | ![局部](http://7xv39r.com1.z0.glb.clouddn.com/2017-11-02-vertex.png) 215 | 216 | 上图是放大5倍后的底图局部,蓝色端点的周围和内部充斥着大量噪点(杂色块)。事实上在「端点识别」过程中,由于噪点的存在,把原本的端点被分解成十几个或数十个小端点了,以下是跑过「端点识别算法」后的底图: 217 | 218 | ![识别后](http://7xv39r.com1.z0.glb.clouddn.com/2017-11-02-after-scan.png) 219 | 220 | 通过上图,可以直观地得出一个结论:识别出来的小端点只在目标(大)端点上集中分布,并且大端点范围内的小端点叠加交错。 221 | 222 | 如果把叠加交错的小端点归并成一个大端点,那么这个大端点将十分接近目标端点。小端点的归并伪代码如下: 223 | 224 | ```javascript 225 | for(let i = 0, len = vertexes.length; i < len - 1; ++i) { 226 | let vertexA = vertexes[i]; 227 | if(vertextA === undefined) continue; 228 | // 注意这里 j = 0 而不是 j = i +1 229 | for(let j = 0; j < len; ++j) { 230 | let vertexB = vertexes[j]; 231 | if(vertextB === undefined) continue; 232 | // 点A与点B有叠加,点B合并到点A并删除点B 233 | if(isCross(vertexA, vertexB)) { 234 | vertexA = merge(vertexA, vertexB); 235 | delete vertexA; 236 | } 237 | } 238 | } 239 | ``` 240 | 241 | 加了小端点归并算法后,「端点识别」的准确度就上去了。经笔者本地测试已经可以 100% 识别有损的连通图了。 242 | 243 | ### 线段识别 244 | 245 | 笔者分两个步骤完成「线段识别」: 246 | 247 | 1. 给定的两个端点连接成线,并采集连线上N个「样本点」; 248 | 2. 遍历样本点像素,如果像素色值不等于线段色值则表示这两个端点之间不存在线段 249 | 250 | 如何采集「样式点」是个问题,太密集会影响性能;太疏松精准度不能保证。 251 | 252 | 在笔者面前有两个选择:N 是常量;N 是变量。 253 | 假设 `N === 5`。局部提取「样式点」如下: 254 | 255 | ![局部](http://7xv39r.com1.z0.glb.clouddn.com/2017-11-01-pattern.gif) 256 | 257 | 上图,会识别出三条线段:AB, BC 和 AC。而事实上,AC不能成线,它只是因为 AB 和 BC 视觉上共一线的结果。当然把 N 值向上提高可以解决这个问题,不过 N 作为常量的话,这个常量的取量需要靠经验来判断,果然放弃。 258 | 259 | 为了避免 AB 与 BC 同处一直线时 AC 被识别成线段,其实很简单 ------ **两个「样本点」的间隔小于或等于端点直径**。 260 | 假设 `N = S / (2 * R)`,S 表示两点的距离,R 表示端点半径。局部提取「样式点」如下: 261 | 262 | ![局部](http://7xv39r.com1.z0.glb.clouddn.com/2017-11-01-pattern-2.gif) 263 | 264 | 如上图,成功地绕过了 AC。「线段识别算法」的伪代码实现如下: 265 | ```javascript 266 | for(let i = 0, len = vertexes.length; i < len - 1; ++i) { 267 | let {x: x1, y: y1} = vertexes[i]; 268 | for(let j = i + 1; j < len; ++j) { 269 | let {x: x2, y: y2} = vertexes[j]; 270 | let S = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); 271 | let N = S / (R * 2); 272 | let stepX = (x1 - x2) / N, stepY = (y1 - y2) / n; 273 | while(--N) { 274 | // 样本点不是线段色 275 | if(!isBelongLine(x1 + N * stepX, y1 + N * stepY)) break; 276 | } 277 | // 样本点都合格 ---- 表示两点成线,保存 278 | if(0 === N) lines.push({x1, y1, x2, y2}) 279 | } 280 | } 281 | ``` 282 | 283 | ## 性能优化 284 | 285 | 由于「自动识图」需要对图像的的像素点进行扫描,那么性能确实是个需要关注的问题。笔者设计的「自动识图算法」,在识别图像的过程中需要对图像的像素做两次扫描:「采集色值表」 与 「采集端点」。在扫描次数上其实很难降低了,但是对于一张 `750 * 1334` 的底图来说,「自动识图算法」需要遍历两次长度为 `750 * 1334 * 4 = 4,002,000` 的数组,压力还是会有的。笔者是从压缩被扫描数组的尺寸来提升性能的。 286 | 287 | **被扫描数组的尺寸怎么压缩?** 288 | 笔者直接通过缩小画布的尺寸来达到缩小被扫描数组尺寸的。伪代码如下: 289 | 290 | ```javascript 291 | // 要压缩的倍数 292 | let resolution = 4; 293 | let [width, height] = [img.width / resolution >> 0, img.height / resolution >> 0]; 294 | ctx.drawImage(img, 0, 0, width, height); 295 | let imageData = ctx.getImageData(), data = imageData; 296 | ``` 297 | 把源图片缩小4倍后,得到的图片像素数组只有原来的 `4^2 = 16倍`。这在性能上是很大的提升。 298 | 299 | ## 使用「自动识图」的建议 300 | 301 | 尽管笔者在本地测试的时候可以把所有的「底图」识别出来,但是并不能保证其它开发者上传的图片能否被很好的识别出来。笔者建议,可以把「自动识图」做为一个单独的工具使用。 302 | 303 | 笔者写了一个「自动识图」的单独工具页面:[https://leeenx.github.io/OneStroke/src/plugin.html](https://leeenx.github.io/OneStroke/src/plugin.html) 304 | 可以在这个页面生成对应的关卡配置。 305 | 306 | ## 结语 307 | 308 | 下面是本文介绍的「一笔画」的线上 [DEMO](https://leeenx.github.io/OneStroke/src/onestroke.html) 的二维码: 309 | 310 | ![demo](http://7xv39r.com1.z0.glb.clouddn.com/2017-10-31-qr.png?v=2) 311 | 312 | 游戏的源码托管在:[https://github.com/leeenx/OneStroke](https://github.com/leeenx/OneStroke) 313 | 其中游戏实现的主体代码在:[https://github.com/leeenx/OneStroke/blob/master/src/script/onestroke.es6](https://github.com/leeenx/OneStroke/blob/master/src/script/onestroke.es6) 314 | 自动识图的代码在:[https://github.com/leeenx/OneStroke/blob/master/src/script/oneStrokePlugin.es6](https://github.com/leeenx/OneStroke/blob/master/src/script/oneStrokePlugin.es6) 315 | 316 | 317 | 感谢耐心阅读完本文章的读者。本文仅代表笔者的个人观点,如有不妥之处请不吝赐教。 318 | -------------------------------------------------------------------------------- /src/script/oneStrokePlugin.es6: -------------------------------------------------------------------------------- 1 | /* 2 | @ 一笔画插件 3 | */ 4 | 5 | export default class OneStrokePlugin { 6 | constructor() { 7 | // 没什么事做 8 | } 9 | 10 | // 图片转换成对应格式 11 | parse(img, name) { 12 | return new Promise((resolve, reject) => { 13 | this.name = name; 14 | // 字符串 15 | if(typeof(img) === "string") { 16 | let src = img; 17 | img = new Image(); 18 | img.crossOrigin = "*"; 19 | img.src = src; 20 | } 21 | // 图片对象 22 | if(img instanceof Image === true) { 23 | // 已经加载完成 24 | if(img.complete === true) resolve(this.scan(img)); 25 | // 未加载完成等待 26 | else { 27 | img.onload = () => resolve(this.scan(img)); 28 | img.onerror = (err) => reject(err); 29 | } 30 | } 31 | }); 32 | } 33 | 34 | /* 35 | @ 扫描识别线段 36 | @ 考虑到图片可能来自截图,所以添加两个参数:head & foot 表示图像的头与尾的占位 37 | */ 38 | scan(img, resolution = 4, head = 120, foot = 120) { 39 | head = head / resolution + 1 >> 0; foot = foot / resolution + 1 >> 0; 40 | // 颜色集 41 | let colors = new Map(); 42 | let canvas = document.createElement("canvas"); 43 | let ctx = canvas.getContext("2d"); 44 | // 宽度是固定的750 45 | let width = 750; 46 | // img.with / width 的比率 47 | let ratio = width / img.width; 48 | // 高度计算 49 | let height = img.height * ratio; 50 | 51 | // 按分辨率压缩 52 | width = width / resolution >> 0; 53 | height = height / resolution >> 0; 54 | canvas.width = width; 55 | canvas.height = height; 56 | 57 | ctx.drawImage(img, 0, 0, width, height); 58 | 59 | // imageData 60 | let imageData = ctx.getImageData(0, 0, width, height); 61 | let data = imageData.data; 62 | 63 | // 起始索引 64 | let startIndex = head * width * 4; 65 | // 终点索引 66 | let endIndex = data.length - foot * width * 4; 67 | 68 | /* 69 | @ 收集颜色 70 | @ 扫描图像并收集图像的所有颜色 71 | @ 由于底色被认定为是白色或透明,所以白色和透明不收集 72 | @ 考虑到噪点对图像的影响,所以把接近白色的点也视作白色 73 | */ 74 | // 线段颜色 75 | let lineColor = [0, 0, 0, 0, 0]; 76 | 77 | // 端点颜色 78 | let vertexColor = [0, 0, 0, 0, 0]; 79 | 80 | // 判断是否属于线段 81 | let isBelongLine = (r, g, b, a) => { 82 | return Math.abs(lineColor[0] - r) <= 10 && Math.abs(lineColor[1] - g) <= 10 && Math.abs(lineColor[2] - b) <= 10; 83 | } 84 | 85 | // 判断是否属于端点 86 | let isBelongVertex = (r, g, b, a) => { 87 | return Math.abs(vertexColor[0] - r) <= 10 && Math.abs(vertexColor[1] - g) <= 10 && Math.abs(vertexColor[2] - b) <= 10; 88 | } 89 | 90 | // 判断是否属于底色 91 | let isBelongBackground = (r, g, b, a) => { 92 | return 255 - r <= 20 && 255 - g <= 20 && 255 - b <= 20 && 255 - a <= 20; 93 | } 94 | 95 | // 扫描像素 96 | for(let i = startIndex; i < endIndex; i += 4) { 97 | let r = data[i], g = data[i + 1], b = data[i + 2], a = data[i + 3]; 98 | // 过滤白色/透明/接近白色 99 | if(a ===0 || isBelongBackground(r, g, b, a)) { 100 | continue; 101 | } 102 | let rgba = "rgba(" + r + "," + g + "," + b + "," + a + ")"; 103 | if(colors.has(rgba) === true) { 104 | let color = colors.get(rgba); 105 | color[4]++; 106 | } 107 | else { 108 | colors.set(rgba, [r, g, b, a, 1]); 109 | } 110 | } 111 | 112 | // 颜色最多的是线段的颜色 113 | for(let color of colors.values()) { 114 | let countA = color[4], countB = lineColor[4]; 115 | if(countA > countB) lineColor = color; 116 | } 117 | 118 | // 颜色第二多的是点的颜色 119 | for(let color of colors.values()) { 120 | let [r, g, b, a] = [color[0], color[1], color[2], color[3]]; 121 | if(isBelongLine(r, g, b, a)) { 122 | continue; 123 | } 124 | let countA = color[4], countB = vertexColor[4]; 125 | if(countA > countB) vertexColor = color; 126 | } 127 | 128 | /* 129 | @ 收集图像中的端点 130 | @ 为了方便处理,把端点视作矩形(rect) 131 | */ 132 | let vertexes = []; 133 | let collect = (index) => { 134 | // 端点的边界 --- 用一个 rect 表示 135 | let top = index / (width * 4) + 1 >> 0, right = (index % (width * 4)) / 4 >> 0, bottom = top, left = right; 136 | // RGBA 137 | let r = data[index], g = data[index + 1], b = data[index + 2], a = data[index + 3]; 138 | while(isBelongVertex(r, g, b, a)) { 139 | // 删除水平方向的点 140 | let boundary = clearHorizontal(index); 141 | left = Math.min(left, boundary.left); 142 | right = Math.max(right, boundary.right); 143 | // 点往下移动 144 | index += width * 4; 145 | r = data[index], g = data[index + 1], b = data[index + 2], a = data[index + 3]; 146 | // bottom 像素加1 147 | ++bottom; 148 | } 149 | // 将点存入 vertexes 150 | vertexes.push({top, right, bottom, left}); 151 | } 152 | 153 | /* 154 | @ 清空指定点左右同色的像素 155 | */ 156 | let clearHorizontal = (index) => { 157 | // 左坐标 158 | let leftIndex = index - 4; 159 | // 向左 160 | while(isBelongVertex(data[leftIndex], data[leftIndex + 1], data[leftIndex + 2], data[leftIndex + 3])) { 161 | // 把 a 设置为 0 表示清除 162 | data[leftIndex + 3] = 0; 163 | // 向左移动 164 | leftIndex -= 4; 165 | } 166 | // 右坐标 167 | let rightIndex = index; 168 | // 向右 169 | while(isBelongVertex(data[rightIndex], data[rightIndex + 1], data[rightIndex + 2], data[rightIndex + 3])) { 170 | // 把 a 设置为 0 表示清除 171 | data[rightIndex + 3] = 0; 172 | // 向左移动 173 | rightIndex -= 4; 174 | } 175 | 176 | let left = (leftIndex % (width * 4)) / 4 >> 0, right = (rightIndex % (width * 4)) / 4 >> 0; 177 | return {left, right}; 178 | } 179 | 180 | // 矩形相交 181 | let isRectCross = (rectA, rectB) => { 182 | let {top: topA, right: rightA, bottom: bottomA, left: leftA} = rectA; 183 | let {top: topB, right: rightB, bottom: bottomB, left: leftB} = rectB; 184 | // 判断垂直方向是否具备相交条件 185 | if(topA <= topB && bottomA >= topB || topB <= topA && bottomB >= topA) { 186 | // 判断水平方向是否具备相交条件 187 | if(leftA <= leftB && rightA >= leftB || leftB <= leftA && rightB >= leftA) { 188 | return true; 189 | } 190 | } 191 | return false; 192 | } 193 | 194 | // 合并矩形 195 | let mergeRect = (rectA, rectB) => { 196 | return { 197 | top: Math.min(rectA.top, rectB.top), 198 | right: Math.max(rectA.right, rectB.right), 199 | bottom: Math.max(rectA.bottom, rectB.bottom), 200 | left: Math.min(rectA.left, rectB.left) 201 | } 202 | } 203 | 204 | // 扫描图像 205 | for(let i = startIndex; i < endIndex; i += 4) { 206 | let r = data[i], g = data[i + 1], b = data[i + 2], a = data[i + 3]; 207 | // 过滤白色/透明/接近白色 208 | if(a === 0 || isBelongBackground(r, g, b, a)) { 209 | continue; 210 | } 211 | // 遇到端点 212 | else if(isBelongVertex(r, g, b, a)) { 213 | // 收集端点 214 | collect(i); 215 | } 216 | } 217 | 218 | // 由于噪点的影响 vertexes 并不精准,需要进行一次归并 219 | for(let i = 0; i < vertexes.length - 1; ++i) { 220 | let rectA = vertexes[i]; 221 | // 跳过被删除的节点 222 | if(!rectA) continue; 223 | for(let j = 0; j < vertexes.length; ++j) { 224 | let rectB = vertexes[j]; 225 | // 跳过被删除的节点 226 | if(i === j || !rectB) continue; 227 | // 矩形相交 228 | if(isRectCross(rectA, rectB) === true) { 229 | // 合并矩形 230 | rectA = vertexes[i] = mergeRect(rectA, rectB); 231 | // 删除 rectB 232 | delete vertexes[j]; 233 | } 234 | } 235 | } 236 | 237 | // 端点的中心坐标 238 | let coords = []; 239 | // 端点的半径 240 | let radius = 0; 241 | // 过滤空洞 242 | vertexes.forEach((rect) => { 243 | let w = rect.right - rect.left, h = rect.bottom - rect.top; 244 | // 半径取最大值 245 | radius = Math.max(radius, w / 2, h / 2) >> 0; 246 | coords.push([rect.left + w / 2 >> 0, rect.top + h / 2 >> 0]); 247 | }); 248 | 249 | // 最终的线段 250 | let lines = []; 251 | 252 | /* 253 | @ 扫描两点之间是否存在线段 254 | @ 思路:均分断点 255 | @ AB间均分为 n 段,此时 A ---> B 之间均匀分布着 n - 1 个点 256 | @ 校验这n个点是否属于线段 257 | */ 258 | 259 | for(let i = 0, len = coords.length; i < len - 1; ++i) { 260 | let aX = coords[i][0], aY = coords[i][1]; 261 | for(let j = i + 1; j < len; ++j) { 262 | let bX = coords[j][0], bY = coords[j][1]; 263 | // AB 的距离 264 | let distance = Math.sqrt(Math.pow(aX - bX, 2) + Math.pow(aY - bY, 2)); 265 | // AB 均分为 n 个子线段,每个子线段的长度不得大于端点的直径,避免漏扫描 266 | let n = distance / (2 * radius) >> 0; 267 | // 子线段的步长(分X与Y) 268 | let stepX = (bX - aX) / n, stepY = (bY - aY) / n; 269 | while(--n > 0) { 270 | let index = (aX + stepX * n >> 0) * 4+ (aY + stepY * n >> 0) * width * 4; 271 | let [r, g, b, a] = [data[index], data[index + 1], data[index + 2], data[index + 3]]; 272 | // 断点没有落在线段上 273 | if(!isBelongLine(r, g, b, a)) break; 274 | } 275 | 276 | // 被检验的点都在线段上,表示 AB 成线 277 | if(0 === n) { 278 | // 还原尺寸 279 | lines.push( 280 | { 281 | x1: coords[i][0] * resolution, 282 | y1: coords[i][1] * resolution, 283 | x2: coords[j][0] * resolution, 284 | y2: coords[j][1] * resolution 285 | } 286 | ); 287 | } 288 | } 289 | } 290 | 291 | // 删除对象 292 | canvas = colors = imageData = data = null; 293 | // return {vertexColor, lineColor, lines}; 294 | // 端点颜色 295 | let baseVertexColor = vertexColor[0] * Math.pow(256, 2) + vertexColor[1] * Math.pow(256, 1) + vertexColor[2]; 296 | // 线段颜色 297 | let baseLineColor = lineColor[0] * Math.pow(256, 2) + lineColor[1] * Math.pow(256, 1) + lineColor[2]; 298 | // 手绘线的颜色取端点的半色值 299 | let strokeColor = vertexColor[0] * Math.pow(256, 2) / 2 + vertexColor[1] * Math.pow(256, 1) / 2 + vertexColor[2] / 2; 300 | 301 | return { 302 | lineColor: baseLineColor, 303 | vertexColor: baseVertexColor, 304 | strokeColor: strokeColor, 305 | activeVertexColor: baseVertexColor, 306 | lines: lines 307 | } 308 | } 309 | } 310 | 311 | 312 | -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/ColorPropsPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: beta 1.5.2 3 | * DATE: 2017-06-19 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | **/ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | var _numExp = /(\d|\.)+/g, 18 | _relNumExp = /(?:\d|\-\d|\.\d|\-\.\d|\+=\d|\-=\d|\+=.\d|\-=\.\d)+/g, 19 | _colorLookup = {aqua:[0,255,255], 20 | lime:[0,255,0], 21 | silver:[192,192,192], 22 | black:[0,0,0], 23 | maroon:[128,0,0], 24 | teal:[0,128,128], 25 | blue:[0,0,255], 26 | navy:[0,0,128], 27 | white:[255,255,255], 28 | fuchsia:[255,0,255], 29 | olive:[128,128,0], 30 | yellow:[255,255,0], 31 | orange:[255,165,0], 32 | gray:[128,128,128], 33 | purple:[128,0,128], 34 | green:[0,128,0], 35 | red:[255,0,0], 36 | pink:[255,192,203], 37 | cyan:[0,255,255], 38 | transparent:[255,255,255,0]}, 39 | _hue = function(h, m1, m2) { 40 | h = (h < 0) ? h + 1 : (h > 1) ? h - 1 : h; 41 | return ((((h * 6 < 1) ? m1 + (m2 - m1) * h * 6 : (h < 0.5) ? m2 : (h * 3 < 2) ? m1 + (m2 - m1) * (2 / 3 - h) * 6 : m1) * 255) + 0.5) | 0; 42 | }, 43 | /** 44 | * @private Parses a color (like #9F0, #FF9900, rgb(255,51,153) or hsl(108, 50%, 10%)) into an array with 3 elements for red, green, and blue or if toHSL parameter is true, it will populate the array with hue, saturation, and lightness values. If a relative value is found in an hsl() or hsla() string, it will preserve those relative prefixes and all the values in the array will be strings instead of numbers (in all other cases it will be populated with numbers). 45 | * @param {(string|number)} v The value the should be parsed which could be a string like #9F0 or rgb(255,102,51) or rgba(255,0,0,0.5) or it could be a number like 0xFF00CC or even a named color like red, blue, purple, etc. 46 | * @param {(boolean)} toHSL If true, an hsl() or hsla() value will be returned instead of rgb() or rgba() 47 | * @return {Array.} An array containing red, green, and blue (and optionally alpha) in that order, or if the toHSL parameter was true, the array will contain hue, saturation and lightness (and optionally alpha) in that order. Always numbers unless there's a relative prefix found in an hsl() or hsla() string and toHSL is true. 48 | */ 49 | _parseColor = function(v, toHSL) { 50 | var a, r, g, b, h, s, l, max, min, d, wasHSL; 51 | if (!v) { 52 | a = _colorLookup.black; 53 | } else if (typeof(v) === "number") { 54 | a = [v >> 16, (v >> 8) & 255, v & 255]; 55 | } else { 56 | if (v.charAt(v.length - 1) === ",") { //sometimes a trailing comma is included and we should chop it off (typically from a comma-delimited list of values like a textShadow:"2px 2px 2px blue, 5px 5px 5px rgb(255,0,0)" - in this example "blue," has a trailing comma. We could strip it out inside parseComplex() but we'd need to do it to the beginning and ending values plus it wouldn't provide protection from other potential scenarios like if the user passes in a similar value. 57 | v = v.substr(0, v.length - 1); 58 | } 59 | if (_colorLookup[v]) { 60 | a = _colorLookup[v]; 61 | } else if (v.charAt(0) === "#") { 62 | if (v.length === 4) { //for shorthand like #9F0 63 | r = v.charAt(1); 64 | g = v.charAt(2); 65 | b = v.charAt(3); 66 | v = "#" + r + r + g + g + b + b; 67 | } 68 | v = parseInt(v.substr(1), 16); 69 | a = [v >> 16, (v >> 8) & 255, v & 255]; 70 | } else if (v.substr(0, 3) === "hsl") { 71 | a = wasHSL = v.match(_numExp); 72 | if (!toHSL) { 73 | h = (Number(a[0]) % 360) / 360; 74 | s = Number(a[1]) / 100; 75 | l = Number(a[2]) / 100; 76 | g = (l <= 0.5) ? l * (s + 1) : l + s - l * s; 77 | r = l * 2 - g; 78 | if (a.length > 3) { 79 | a[3] = Number(v[3]); 80 | } 81 | a[0] = _hue(h + 1 / 3, r, g); 82 | a[1] = _hue(h, r, g); 83 | a[2] = _hue(h - 1 / 3, r, g); 84 | } else if (v.indexOf("=") !== -1) { //if relative values are found, just return the raw strings with the relative prefixes in place. 85 | return v.match(_relNumExp); 86 | } 87 | } else { 88 | a = v.match(_numExp) || _colorLookup.transparent; 89 | } 90 | a[0] = Number(a[0]); 91 | a[1] = Number(a[1]); 92 | a[2] = Number(a[2]); 93 | if (a.length > 3) { 94 | a[3] = Number(a[3]); 95 | } 96 | } 97 | if (toHSL && !wasHSL) { 98 | r = a[0] / 255; 99 | g = a[1] / 255; 100 | b = a[2] / 255; 101 | max = Math.max(r, g, b); 102 | min = Math.min(r, g, b); 103 | l = (max + min) / 2; 104 | if (max === min) { 105 | h = s = 0; 106 | } else { 107 | d = max - min; 108 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 109 | h = (max === r) ? (g - b) / d + (g < b ? 6 : 0) : (max === g) ? (b - r) / d + 2 : (r - g) / d + 4; 110 | h *= 60; 111 | } 112 | a[0] = (h + 0.5) | 0; 113 | a[1] = (s * 100 + 0.5) | 0; 114 | a[2] = (l * 100 + 0.5) | 0; 115 | } 116 | return a; 117 | }, 118 | _formatColors = function(s, toHSL) { 119 | var colors = (s + "").match(_colorExp) || [], 120 | charIndex = 0, 121 | parsed = "", 122 | i, color, temp; 123 | if (!colors.length) { 124 | return s; 125 | } 126 | for (i = 0; i < colors.length; i++) { 127 | color = colors[i]; 128 | temp = s.substr(charIndex, s.indexOf(color, charIndex)-charIndex); 129 | charIndex += temp.length + color.length; 130 | color = _parseColor(color, toHSL); 131 | if (color.length === 3) { 132 | color.push(1); 133 | } 134 | parsed += temp + (toHSL ? "hsla(" + color[0] + "," + color[1] + "%," + color[2] + "%," + color[3] : "rgba(" + color.join(",")) + ")"; 135 | } 136 | return parsed + s.substr(charIndex); 137 | }, p, _colorStringFilter, 138 | TweenLite = (_gsScope.GreenSockGlobals || _gsScope).TweenLite, 139 | _colorExp = "(?:\\b(?:(?:rgb|rgba|hsl|hsla)\\(.+?\\))|\\B#(?:[0-9a-f]{3}){1,2}\\b", //we'll dynamically build this Regular Expression to conserve file size. After building it, it will be able to find rgb(), rgba(), # (hexadecimal), and named color values like red, blue, purple, etc. 140 | 141 | ColorPropsPlugin = _gsScope._gsDefine.plugin({ 142 | propName: "colorProps", 143 | version: "1.5.2", 144 | priority: -1, 145 | API: 2, 146 | global: true, 147 | 148 | //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 149 | init: function(target, value, tween, index) { 150 | var p, proxy, pt, val; 151 | this._target = target; 152 | this._proxy = proxy = ((value.format + "").toUpperCase() === "NUMBER") ? {} : 0; 153 | for (p in value) { 154 | if (p !== "format") { 155 | if (proxy) { 156 | this._firstNumPT = pt = {_next:this._firstNumPT, t:target, p:p, f:(typeof(target[p]) === "function")}; 157 | proxy[p] = "rgb(" + _parseColor(!pt.f ? target[p] : target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ]()).join(",") + ")"; 158 | val = value[p]; 159 | if (typeof(val) === "function") { 160 | val = val(index, target); 161 | } 162 | this._addTween(proxy, p, "get", ((typeof(val) === "number") ? "rgb(" + _parseColor(val, false).join(",") + ")" : val), p, null, null, _colorStringFilter); 163 | } else { 164 | this._addTween(target, p, "get", value[p], p, null, null, _colorStringFilter, index); 165 | } 166 | 167 | } 168 | } 169 | return true; 170 | }, 171 | 172 | //called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.) 173 | set: function(v) { 174 | var pt = this._firstNumPT, 175 | val; 176 | this._super.setRatio.call(this, v); 177 | while (pt) { 178 | val = _parseColor(this._proxy[pt.p], false); 179 | val = val[0] << 16 | val[1] << 8 | val[2]; 180 | if (pt.f) { 181 | this._target[pt.p](val); 182 | } else { 183 | this._target[pt.p] = val; 184 | } 185 | pt = pt._next; 186 | } 187 | } 188 | }); 189 | 190 | for (p in _colorLookup) { 191 | _colorExp += "|" + p + "\\b"; 192 | } 193 | _colorExp = new RegExp(_colorExp+")", "gi"); 194 | ColorPropsPlugin.colorStringFilter = _colorStringFilter = function(a) { 195 | var combined = a[0] + " " + a[1], 196 | toHSL; 197 | _colorExp.lastIndex = 0; 198 | if (_colorExp.test(combined)) { 199 | toHSL = (combined.indexOf("hsl(") !== -1 || combined.indexOf("hsla(") !== -1); 200 | a[0] = _formatColors(a[0], toHSL); 201 | a[1] = _formatColors(a[1], toHSL); 202 | } 203 | }; 204 | 205 | if (!TweenLite.defaultStringFilter) { 206 | TweenLite.defaultStringFilter = ColorPropsPlugin.colorStringFilter; 207 | } 208 | 209 | ColorPropsPlugin.parseColor = _parseColor; 210 | p = ColorPropsPlugin.prototype; 211 | p._firstNumPT = null; 212 | p._kill = function(lookup) { 213 | var pt = this._firstNumPT, 214 | prev; 215 | while (pt) { 216 | if (pt.p in lookup) { 217 | if (pt === p._firstNumPT) { 218 | this._firstNumPT = pt._next; 219 | } 220 | if (prev) { 221 | prev._next = pt._next; 222 | } 223 | } else { 224 | prev = pt; 225 | } 226 | pt = pt._next; 227 | } 228 | return this._super._kill(lookup); 229 | }; 230 | 231 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 232 | 233 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 234 | (function(name) { 235 | "use strict"; 236 | var getGlobal = function() { 237 | return (_gsScope.GreenSockGlobals || _gsScope)[name]; 238 | }; 239 | if (typeof(module) !== "undefined" && module.exports) { //node 240 | require("../TweenLite.js"); 241 | module.exports = getGlobal(); 242 | } else if (typeof(define) === "function" && define.amd) { //AMD 243 | define(["TweenLite"], getGlobal); 244 | } 245 | }("ColorPropsPlugin")); -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/EaselPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 0.2.2 3 | * DATE: 2017-06-19 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | **/ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | var _numExp = /(\d|\.)+/g, 18 | _ColorFilter, _ColorMatrixFilter, 19 | _colorProps = ["redMultiplier","greenMultiplier","blueMultiplier","alphaMultiplier","redOffset","greenOffset","blueOffset","alphaOffset"], 20 | _colorLookup = {aqua:[0,255,255], 21 | lime:[0,255,0], 22 | silver:[192,192,192], 23 | black:[0,0,0], 24 | maroon:[128,0,0], 25 | teal:[0,128,128], 26 | blue:[0,0,255], 27 | navy:[0,0,128], 28 | white:[255,255,255], 29 | fuchsia:[255,0,255], 30 | olive:[128,128,0], 31 | yellow:[255,255,0], 32 | orange:[255,165,0], 33 | gray:[128,128,128], 34 | purple:[128,0,128], 35 | green:[0,128,0], 36 | red:[255,0,0], 37 | pink:[255,192,203], 38 | cyan:[0,255,255], 39 | transparent:[255,255,255,0]}, 40 | _parseColor = function(color) { 41 | if (color === "" || color == null || color === "none") { 42 | return _colorLookup.transparent; 43 | } else if (_colorLookup[color]) { 44 | return _colorLookup[color]; 45 | } else if (typeof(color) === "number") { 46 | return [color >> 16, (color >> 8) & 255, color & 255]; 47 | } else if (color.charAt(0) === "#") { 48 | if (color.length === 4) { //for shorthand like #9F0 49 | color = "#" + color.charAt(1) + color.charAt(1) + color.charAt(2) + color.charAt(2) + color.charAt(3) + color.charAt(3); 50 | } 51 | color = parseInt(color.substr(1), 16); 52 | return [color >> 16, (color >> 8) & 255, color & 255]; 53 | } 54 | return color.match(_numExp) || _colorLookup.transparent; 55 | }, 56 | _parseColorFilter = function(t, v, pg) { 57 | if (!_ColorFilter) { 58 | _ColorFilter = (_gsScope.ColorFilter || _gsScope.createjs.ColorFilter); 59 | if (!_ColorFilter) { 60 | throw("EaselPlugin error: The EaselJS ColorFilter JavaScript file wasn't loaded."); 61 | } 62 | } 63 | var filters = t.filters || [], 64 | i = filters.length, 65 | c, s, e, a, p; 66 | while (--i > -1) { 67 | if (filters[i] instanceof _ColorFilter) { 68 | s = filters[i]; 69 | break; 70 | } 71 | } 72 | if (!s) { 73 | s = new _ColorFilter(); 74 | filters.push(s); 75 | t.filters = filters; 76 | } 77 | e = s.clone(); 78 | if (v.tint != null) { 79 | c = _parseColor(v.tint); 80 | a = (v.tintAmount != null) ? Number(v.tintAmount) : 1; 81 | e.redOffset = Number(c[0]) * a; 82 | e.greenOffset = Number(c[1]) * a; 83 | e.blueOffset = Number(c[2]) * a; 84 | e.redMultiplier = e.greenMultiplier = e.blueMultiplier = 1 - a; 85 | } else { 86 | for (p in v) { 87 | if (p !== "exposure") if (p !== "brightness") { 88 | e[p] = Number(v[p]); 89 | } 90 | } 91 | } 92 | if (v.exposure != null) { 93 | e.redOffset = e.greenOffset = e.blueOffset = 255 * (Number(v.exposure) - 1); 94 | e.redMultiplier = e.greenMultiplier = e.blueMultiplier = 1; 95 | } else if (v.brightness != null) { 96 | a = Number(v.brightness) - 1; 97 | e.redOffset = e.greenOffset = e.blueOffset = (a > 0) ? a * 255 : 0; 98 | e.redMultiplier = e.greenMultiplier = e.blueMultiplier = 1 - Math.abs(a); 99 | } 100 | i = 8; 101 | while (--i > -1) { 102 | p = _colorProps[i]; 103 | if (s[p] !== e[p]) { 104 | pg._addTween(s, p, s[p], e[p], "easel_colorFilter"); 105 | } 106 | } 107 | pg._overwriteProps.push("easel_colorFilter"); 108 | if (!t.cacheID) { 109 | throw("EaselPlugin warning: for filters to display in EaselJS, you must call the object's cache() method first. " + t); 110 | } 111 | }, 112 | 113 | _idMatrix = [1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0], 114 | _lumR = 0.212671, 115 | _lumG = 0.715160, 116 | _lumB = 0.072169, 117 | 118 | _applyMatrix = function(m, m2) { 119 | if (!(m instanceof Array) || !(m2 instanceof Array)) { 120 | return m2; 121 | } 122 | var temp = [], 123 | i = 0, 124 | z = 0, 125 | y, x; 126 | for (y = 0; y < 4; y++) { 127 | for (x = 0; x < 5; x++) { 128 | z = (x === 4) ? m[i + 4] : 0; 129 | temp[i + x] = m[i] * m2[x] + m[i+1] * m2[x + 5] + m[i+2] * m2[x + 10] + m[i+3] * m2[x + 15] + z; 130 | } 131 | i += 5; 132 | } 133 | return temp; 134 | }, 135 | 136 | _setSaturation = function(m, n) { 137 | if (isNaN(n)) { 138 | return m; 139 | } 140 | var inv = 1 - n, 141 | r = inv * _lumR, 142 | g = inv * _lumG, 143 | b = inv * _lumB; 144 | return _applyMatrix([r + n, g, b, 0, 0, r, g + n, b, 0, 0, r, g, b + n, 0, 0, 0, 0, 0, 1, 0], m); 145 | }, 146 | 147 | _colorize = function(m, color, amount) { 148 | if (isNaN(amount)) { 149 | amount = 1; 150 | } 151 | var c = _parseColor(color), 152 | r = c[0] / 255, 153 | g = c[1] / 255, 154 | b = c[2] / 255, 155 | inv = 1 - amount; 156 | return _applyMatrix([inv + amount * r * _lumR, amount * r * _lumG, amount * r * _lumB, 0, 0, amount * g * _lumR, inv + amount * g * _lumG, amount * g * _lumB, 0, 0, amount * b * _lumR, amount * b * _lumG, inv + amount * b * _lumB, 0, 0, 0, 0, 0, 1, 0], m); 157 | }, 158 | 159 | _setHue = function(m, n) { 160 | if (isNaN(n)) { 161 | return m; 162 | } 163 | n *= Math.PI / 180; 164 | var c = Math.cos(n), 165 | s = Math.sin(n); 166 | return _applyMatrix([(_lumR + (c * (1 - _lumR))) + (s * (-_lumR)), (_lumG + (c * (-_lumG))) + (s * (-_lumG)), (_lumB + (c * (-_lumB))) + (s * (1 - _lumB)), 0, 0, (_lumR + (c * (-_lumR))) + (s * 0.143), (_lumG + (c * (1 - _lumG))) + (s * 0.14), (_lumB + (c * (-_lumB))) + (s * -0.283), 0, 0, (_lumR + (c * (-_lumR))) + (s * (-(1 - _lumR))), (_lumG + (c * (-_lumG))) + (s * _lumG), (_lumB + (c * (1 - _lumB))) + (s * _lumB), 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1], m); 167 | }, 168 | 169 | _setContrast = function(m, n) { 170 | if (isNaN(n)) { 171 | return m; 172 | } 173 | n += 0.01; 174 | return _applyMatrix([n,0,0,0,128 * (1 - n), 0,n,0,0,128 * (1 - n), 0,0,n,0,128 * (1 - n), 0,0,0,1,0], m); 175 | }, 176 | 177 | _parseColorMatrixFilter = function(t, v, pg) { 178 | if (!_ColorMatrixFilter) { 179 | _ColorMatrixFilter = (_gsScope.ColorMatrixFilter || _gsScope.createjs.ColorMatrixFilter); 180 | if (!_ColorMatrixFilter) { 181 | throw("EaselPlugin error: The EaselJS ColorMatrixFilter JavaScript file wasn't loaded."); 182 | } 183 | } 184 | var filters = t.filters || [], 185 | i = filters.length, 186 | matrix, startMatrix, s; 187 | while (--i > -1) { 188 | if (filters[i] instanceof _ColorMatrixFilter) { 189 | s = filters[i]; 190 | break; 191 | } 192 | } 193 | if (!s) { 194 | s = new _ColorMatrixFilter(_idMatrix.slice()); 195 | filters.push(s); 196 | t.filters = filters; 197 | } 198 | startMatrix = s.matrix; 199 | matrix = _idMatrix.slice(); 200 | if (v.colorize != null) { 201 | matrix = _colorize(matrix, v.colorize, Number(v.colorizeAmount)); 202 | } 203 | if (v.contrast != null) { 204 | matrix = _setContrast(matrix, Number(v.contrast)); 205 | } 206 | if (v.hue != null) { 207 | matrix = _setHue(matrix, Number(v.hue)); 208 | } 209 | if (v.saturation != null) { 210 | matrix = _setSaturation(matrix, Number(v.saturation)); 211 | } 212 | 213 | i = matrix.length; 214 | while (--i > -1) { 215 | if (matrix[i] !== startMatrix[i]) { 216 | pg._addTween(startMatrix, i, startMatrix[i], matrix[i], "easel_colorMatrixFilter"); 217 | } 218 | } 219 | 220 | pg._overwriteProps.push("easel_colorMatrixFilter"); 221 | if (!t.cacheID) { 222 | throw("EaselPlugin warning: for filters to display in EaselJS, you must call the object's cache() method first. " + t); 223 | } 224 | 225 | pg._matrix = startMatrix; 226 | }; 227 | 228 | 229 | _gsScope._gsDefine.plugin({ 230 | propName: "easel", 231 | priority: -1, 232 | version: "0.2.2", 233 | API: 2, 234 | 235 | //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 236 | init: function(target, value, tween, index) { 237 | this._target = target; 238 | var p, pt, tint, colorMatrix, end, labels, i; 239 | for (p in value) { 240 | 241 | end = value[p]; 242 | if (typeof(end) === "function") { 243 | end = end(index, target); 244 | } 245 | if (p === "colorFilter" || p === "tint" || p === "tintAmount" || p === "exposure" || p === "brightness") { 246 | if (!tint) { 247 | _parseColorFilter(target, value.colorFilter || value, this); 248 | tint = true; 249 | } 250 | 251 | } else if (p === "saturation" || p === "contrast" || p === "hue" || p === "colorize" || p === "colorizeAmount") { 252 | if (!colorMatrix) { 253 | _parseColorMatrixFilter(target, value.colorMatrixFilter || value, this); 254 | colorMatrix = true; 255 | } 256 | 257 | } else if (p === "frame") { 258 | this._firstPT = pt = {_next:this._firstPT, t:target, p:"gotoAndStop", s:target.currentFrame, f:true, n:"frame", pr:0, type:0, m:Math.round}; 259 | if (typeof(end) === "string" && end.charAt(1) !== "=" && (labels = target.labels)) { 260 | for (i = 0; i < labels.length; i++) { 261 | if (labels[i].label === end) { 262 | end = labels[i].position; 263 | } 264 | } 265 | } 266 | pt.c = (typeof(end) === "number") ? end - pt.s : parseFloat((end+"").split("=").join("")); 267 | if (pt._next) { 268 | pt._next._prev = pt; 269 | } 270 | 271 | } else if (target[p] != null) { 272 | this._firstPT = pt = {_next:this._firstPT, t:target, p:p, f:(typeof(target[p]) === "function"), n:p, pr:0, type:0}; 273 | pt.s = (!pt.f) ? parseFloat(target[p]) : target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ](); 274 | pt.c = (typeof(end) === "number") ? end - pt.s : (typeof(end) === "string") ? parseFloat(end.split("=").join("")) : 0; 275 | 276 | if (pt._next) { 277 | pt._next._prev = pt; 278 | } 279 | } 280 | 281 | } 282 | return true; 283 | }, 284 | 285 | //called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.) 286 | set: function(v) { 287 | var pt = this._firstPT, 288 | min = 0.000001, 289 | val; 290 | while (pt) { 291 | val = pt.c * v + pt.s; 292 | if (pt.m) { 293 | val = pt.m(val, pt.t); 294 | } else if (val < min && val > -min) { 295 | val = 0; 296 | } 297 | if (pt.f) { 298 | pt.t[pt.p](val); 299 | } else { 300 | pt.t[pt.p] = val; 301 | } 302 | pt = pt._next; 303 | } 304 | if (this._target.cacheID) { 305 | this._target.updateCache(); 306 | } 307 | } 308 | 309 | }); 310 | 311 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 312 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 313 | (function(name) { 314 | "use strict"; 315 | var getGlobal = function() { 316 | return (_gsScope.GreenSockGlobals || _gsScope)[name]; 317 | }; 318 | if (typeof(module) !== "undefined" && module.exports) { //node 319 | require("../TweenLite.js"); 320 | module.exports = getGlobal(); 321 | } else if (typeof(define) === "function" && define.amd) { //AMD 322 | define(["TweenLite"], getGlobal); 323 | } 324 | }("EaselPlugin")); -------------------------------------------------------------------------------- /src/script/lib/gsap/easing/EasePack.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 1.15.6 3 | * DATE: 2017-06-19 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | **/ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | _gsScope._gsDefine("easing.Back", ["easing.Ease"], function(Ease) { 18 | 19 | var w = (_gsScope.GreenSockGlobals || _gsScope), 20 | gs = w.com.greensock, 21 | _2PI = Math.PI * 2, 22 | _HALF_PI = Math.PI / 2, 23 | _class = gs._class, 24 | _create = function(n, f) { 25 | var C = _class("easing." + n, function(){}, true), 26 | p = C.prototype = new Ease(); 27 | p.constructor = C; 28 | p.getRatio = f; 29 | return C; 30 | }, 31 | _easeReg = Ease.register || function(){}, //put an empty function in place just as a safety measure in case someone loads an OLD version of TweenLite.js where Ease.register doesn't exist. 32 | _wrap = function(name, EaseOut, EaseIn, EaseInOut, aliases) { 33 | var C = _class("easing."+name, { 34 | easeOut:new EaseOut(), 35 | easeIn:new EaseIn(), 36 | easeInOut:new EaseInOut() 37 | }, true); 38 | _easeReg(C, name); 39 | return C; 40 | }, 41 | EasePoint = function(time, value, next) { 42 | this.t = time; 43 | this.v = value; 44 | if (next) { 45 | this.next = next; 46 | next.prev = this; 47 | this.c = next.v - value; 48 | this.gap = next.t - time; 49 | } 50 | }, 51 | 52 | //Back 53 | _createBack = function(n, f) { 54 | var C = _class("easing." + n, function(overshoot) { 55 | this._p1 = (overshoot || overshoot === 0) ? overshoot : 1.70158; 56 | this._p2 = this._p1 * 1.525; 57 | }, true), 58 | p = C.prototype = new Ease(); 59 | p.constructor = C; 60 | p.getRatio = f; 61 | p.config = function(overshoot) { 62 | return new C(overshoot); 63 | }; 64 | return C; 65 | }, 66 | 67 | Back = _wrap("Back", 68 | _createBack("BackOut", function(p) { 69 | return ((p = p - 1) * p * ((this._p1 + 1) * p + this._p1) + 1); 70 | }), 71 | _createBack("BackIn", function(p) { 72 | return p * p * ((this._p1 + 1) * p - this._p1); 73 | }), 74 | _createBack("BackInOut", function(p) { 75 | return ((p *= 2) < 1) ? 0.5 * p * p * ((this._p2 + 1) * p - this._p2) : 0.5 * ((p -= 2) * p * ((this._p2 + 1) * p + this._p2) + 2); 76 | }) 77 | ), 78 | 79 | 80 | //SlowMo 81 | SlowMo = _class("easing.SlowMo", function(linearRatio, power, yoyoMode) { 82 | power = (power || power === 0) ? power : 0.7; 83 | if (linearRatio == null) { 84 | linearRatio = 0.7; 85 | } else if (linearRatio > 1) { 86 | linearRatio = 1; 87 | } 88 | this._p = (linearRatio !== 1) ? power : 0; 89 | this._p1 = (1 - linearRatio) / 2; 90 | this._p2 = linearRatio; 91 | this._p3 = this._p1 + this._p2; 92 | this._calcEnd = (yoyoMode === true); 93 | }, true), 94 | p = SlowMo.prototype = new Ease(), 95 | SteppedEase, RoughEase, _createElastic; 96 | 97 | p.constructor = SlowMo; 98 | p.getRatio = function(p) { 99 | var r = p + (0.5 - p) * this._p; 100 | if (p < this._p1) { 101 | return this._calcEnd ? 1 - ((p = 1 - (p / this._p1)) * p) : r - ((p = 1 - (p / this._p1)) * p * p * p * r); 102 | } else if (p > this._p3) { 103 | return this._calcEnd ? 1 - (p = (p - this._p3) / this._p1) * p : r + ((p - r) * (p = (p - this._p3) / this._p1) * p * p * p); 104 | } 105 | return this._calcEnd ? 1 : r; 106 | }; 107 | SlowMo.ease = new SlowMo(0.7, 0.7); 108 | 109 | p.config = SlowMo.config = function(linearRatio, power, yoyoMode) { 110 | return new SlowMo(linearRatio, power, yoyoMode); 111 | }; 112 | 113 | 114 | //SteppedEase 115 | SteppedEase = _class("easing.SteppedEase", function(steps, immediateStart) { 116 | steps = steps || 1; 117 | this._p1 = 1 / steps; 118 | this._p2 = steps + (immediateStart ? 0 : 1); 119 | this._p3 = immediateStart ? 1 : 0; 120 | }, true); 121 | p = SteppedEase.prototype = new Ease(); 122 | p.constructor = SteppedEase; 123 | p.getRatio = function(p) { 124 | if (p < 0) { 125 | p = 0; 126 | } else if (p >= 1) { 127 | p = 0.999999999; 128 | } 129 | return (((this._p2 * p) | 0) + this._p3) * this._p1; 130 | }; 131 | p.config = SteppedEase.config = function(steps, immediateStart) { 132 | return new SteppedEase(steps, immediateStart); 133 | }; 134 | 135 | 136 | //RoughEase 137 | RoughEase = _class("easing.RoughEase", function(vars) { 138 | vars = vars || {}; 139 | var taper = vars.taper || "none", 140 | a = [], 141 | cnt = 0, 142 | points = (vars.points || 20) | 0, 143 | i = points, 144 | randomize = (vars.randomize !== false), 145 | clamp = (vars.clamp === true), 146 | template = (vars.template instanceof Ease) ? vars.template : null, 147 | strength = (typeof(vars.strength) === "number") ? vars.strength * 0.4 : 0.4, 148 | x, y, bump, invX, obj, pnt; 149 | while (--i > -1) { 150 | x = randomize ? Math.random() : (1 / points) * i; 151 | y = template ? template.getRatio(x) : x; 152 | if (taper === "none") { 153 | bump = strength; 154 | } else if (taper === "out") { 155 | invX = 1 - x; 156 | bump = invX * invX * strength; 157 | } else if (taper === "in") { 158 | bump = x * x * strength; 159 | } else if (x < 0.5) { //"both" (start) 160 | invX = x * 2; 161 | bump = invX * invX * 0.5 * strength; 162 | } else { //"both" (end) 163 | invX = (1 - x) * 2; 164 | bump = invX * invX * 0.5 * strength; 165 | } 166 | if (randomize) { 167 | y += (Math.random() * bump) - (bump * 0.5); 168 | } else if (i % 2) { 169 | y += bump * 0.5; 170 | } else { 171 | y -= bump * 0.5; 172 | } 173 | if (clamp) { 174 | if (y > 1) { 175 | y = 1; 176 | } else if (y < 0) { 177 | y = 0; 178 | } 179 | } 180 | a[cnt++] = {x:x, y:y}; 181 | } 182 | a.sort(function(a, b) { 183 | return a.x - b.x; 184 | }); 185 | 186 | pnt = new EasePoint(1, 1, null); 187 | i = points; 188 | while (--i > -1) { 189 | obj = a[i]; 190 | pnt = new EasePoint(obj.x, obj.y, pnt); 191 | } 192 | 193 | this._prev = new EasePoint(0, 0, (pnt.t !== 0) ? pnt : pnt.next); 194 | }, true); 195 | p = RoughEase.prototype = new Ease(); 196 | p.constructor = RoughEase; 197 | p.getRatio = function(p) { 198 | var pnt = this._prev; 199 | if (p > pnt.t) { 200 | while (pnt.next && p >= pnt.t) { 201 | pnt = pnt.next; 202 | } 203 | pnt = pnt.prev; 204 | } else { 205 | while (pnt.prev && p <= pnt.t) { 206 | pnt = pnt.prev; 207 | } 208 | } 209 | this._prev = pnt; 210 | return (pnt.v + ((p - pnt.t) / pnt.gap) * pnt.c); 211 | }; 212 | p.config = function(vars) { 213 | return new RoughEase(vars); 214 | }; 215 | RoughEase.ease = new RoughEase(); 216 | 217 | 218 | //Bounce 219 | _wrap("Bounce", 220 | _create("BounceOut", function(p) { 221 | if (p < 1 / 2.75) { 222 | return 7.5625 * p * p; 223 | } else if (p < 2 / 2.75) { 224 | return 7.5625 * (p -= 1.5 / 2.75) * p + 0.75; 225 | } else if (p < 2.5 / 2.75) { 226 | return 7.5625 * (p -= 2.25 / 2.75) * p + 0.9375; 227 | } 228 | return 7.5625 * (p -= 2.625 / 2.75) * p + 0.984375; 229 | }), 230 | _create("BounceIn", function(p) { 231 | if ((p = 1 - p) < 1 / 2.75) { 232 | return 1 - (7.5625 * p * p); 233 | } else if (p < 2 / 2.75) { 234 | return 1 - (7.5625 * (p -= 1.5 / 2.75) * p + 0.75); 235 | } else if (p < 2.5 / 2.75) { 236 | return 1 - (7.5625 * (p -= 2.25 / 2.75) * p + 0.9375); 237 | } 238 | return 1 - (7.5625 * (p -= 2.625 / 2.75) * p + 0.984375); 239 | }), 240 | _create("BounceInOut", function(p) { 241 | var invert = (p < 0.5); 242 | if (invert) { 243 | p = 1 - (p * 2); 244 | } else { 245 | p = (p * 2) - 1; 246 | } 247 | if (p < 1 / 2.75) { 248 | p = 7.5625 * p * p; 249 | } else if (p < 2 / 2.75) { 250 | p = 7.5625 * (p -= 1.5 / 2.75) * p + 0.75; 251 | } else if (p < 2.5 / 2.75) { 252 | p = 7.5625 * (p -= 2.25 / 2.75) * p + 0.9375; 253 | } else { 254 | p = 7.5625 * (p -= 2.625 / 2.75) * p + 0.984375; 255 | } 256 | return invert ? (1 - p) * 0.5 : p * 0.5 + 0.5; 257 | }) 258 | ); 259 | 260 | 261 | //CIRC 262 | _wrap("Circ", 263 | _create("CircOut", function(p) { 264 | return Math.sqrt(1 - (p = p - 1) * p); 265 | }), 266 | _create("CircIn", function(p) { 267 | return -(Math.sqrt(1 - (p * p)) - 1); 268 | }), 269 | _create("CircInOut", function(p) { 270 | return ((p*=2) < 1) ? -0.5 * (Math.sqrt(1 - p * p) - 1) : 0.5 * (Math.sqrt(1 - (p -= 2) * p) + 1); 271 | }) 272 | ); 273 | 274 | 275 | //Elastic 276 | _createElastic = function(n, f, def) { 277 | var C = _class("easing." + n, function(amplitude, period) { 278 | this._p1 = (amplitude >= 1) ? amplitude : 1; //note: if amplitude is < 1, we simply adjust the period for a more natural feel. Otherwise the math doesn't work right and the curve starts at 1. 279 | this._p2 = (period || def) / (amplitude < 1 ? amplitude : 1); 280 | this._p3 = this._p2 / _2PI * (Math.asin(1 / this._p1) || 0); 281 | this._p2 = _2PI / this._p2; //precalculate to optimize 282 | }, true), 283 | p = C.prototype = new Ease(); 284 | p.constructor = C; 285 | p.getRatio = f; 286 | p.config = function(amplitude, period) { 287 | return new C(amplitude, period); 288 | }; 289 | return C; 290 | }; 291 | _wrap("Elastic", 292 | _createElastic("ElasticOut", function(p) { 293 | return this._p1 * Math.pow(2, -10 * p) * Math.sin( (p - this._p3) * this._p2 ) + 1; 294 | }, 0.3), 295 | _createElastic("ElasticIn", function(p) { 296 | return -(this._p1 * Math.pow(2, 10 * (p -= 1)) * Math.sin( (p - this._p3) * this._p2 )); 297 | }, 0.3), 298 | _createElastic("ElasticInOut", function(p) { 299 | return ((p *= 2) < 1) ? -0.5 * (this._p1 * Math.pow(2, 10 * (p -= 1)) * Math.sin( (p - this._p3) * this._p2)) : this._p1 * Math.pow(2, -10 *(p -= 1)) * Math.sin( (p - this._p3) * this._p2 ) * 0.5 + 1; 300 | }, 0.45) 301 | ); 302 | 303 | 304 | //Expo 305 | _wrap("Expo", 306 | _create("ExpoOut", function(p) { 307 | return 1 - Math.pow(2, -10 * p); 308 | }), 309 | _create("ExpoIn", function(p) { 310 | return Math.pow(2, 10 * (p - 1)) - 0.001; 311 | }), 312 | _create("ExpoInOut", function(p) { 313 | return ((p *= 2) < 1) ? 0.5 * Math.pow(2, 10 * (p - 1)) : 0.5 * (2 - Math.pow(2, -10 * (p - 1))); 314 | }) 315 | ); 316 | 317 | 318 | //Sine 319 | _wrap("Sine", 320 | _create("SineOut", function(p) { 321 | return Math.sin(p * _HALF_PI); 322 | }), 323 | _create("SineIn", function(p) { 324 | return -Math.cos(p * _HALF_PI) + 1; 325 | }), 326 | _create("SineInOut", function(p) { 327 | return -0.5 * (Math.cos(Math.PI * p) - 1); 328 | }) 329 | ); 330 | 331 | _class("easing.EaseLookup", { 332 | find:function(s) { 333 | return Ease.map[s]; 334 | } 335 | }, true); 336 | 337 | //register the non-standard eases 338 | _easeReg(w.SlowMo, "SlowMo", "ease,"); 339 | _easeReg(RoughEase, "RoughEase", "ease,"); 340 | _easeReg(SteppedEase, "SteppedEase", "ease,"); 341 | 342 | return Back; 343 | 344 | }, true); 345 | 346 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 347 | 348 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 349 | (function() { 350 | "use strict"; 351 | var getGlobal = function() { 352 | return (_gsScope.GreenSockGlobals || _gsScope); 353 | }; 354 | if (typeof(module) !== "undefined" && module.exports) { //node 355 | require("../TweenLite.js"); 356 | module.exports = getGlobal(); 357 | } else if (typeof(define) === "function" && define.amd) { //AMD 358 | define(["TweenLite"], getGlobal); 359 | } 360 | }()); -------------------------------------------------------------------------------- /src/script/plugin.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 1 && arguments[1] !== undefined ? arguments[1] : 4; 66 | var head = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 120; 67 | var foot = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 120; 68 | 69 | head = head / resolution + 1 >> 0;foot = foot / resolution + 1 >> 0; 70 | // 颜色集 71 | var colors = new Map(); 72 | var canvas = document.createElement("canvas"); 73 | var ctx = canvas.getContext("2d"); 74 | // 宽度是固定的750 75 | var width = 750; 76 | // img.with / width 的比率 77 | var ratio = width / img.width; 78 | // 高度计算 79 | var height = img.height * ratio; 80 | 81 | // 按分辨率压缩 82 | width = width / resolution >> 0; 83 | height = height / resolution >> 0; 84 | canvas.width = width; 85 | canvas.height = height; 86 | 87 | ctx.drawImage(img, 0, 0, width, height); 88 | 89 | // imageData 90 | var imageData = ctx.getImageData(0, 0, width, height); 91 | var data = imageData.data; 92 | 93 | // 起始索引 94 | var startIndex = head * width * 4; 95 | // 终点索引 96 | var endIndex = data.length - foot * width * 4; 97 | 98 | /* 99 | @ 收集颜色 100 | @ 扫描图像并收集图像的所有颜色 101 | @ 由于底色被认定为是白色或透明,所以白色和透明不收集 102 | @ 考虑到噪点对图像的影响,所以把接近白色的点也视作白色 103 | */ 104 | // 线段颜色 105 | var lineColor = [0, 0, 0, 0, 0]; 106 | 107 | // 端点颜色 108 | var vertexColor = [0, 0, 0, 0, 0]; 109 | 110 | // 判断是否属于线段 111 | var isBelongLine = function isBelongLine(r, g, b, a) { 112 | return Math.abs(lineColor[0] - r) <= 10 && Math.abs(lineColor[1] - g) <= 10 && Math.abs(lineColor[2] - b) <= 10; 113 | }; 114 | 115 | // 判断是否属于端点 116 | var isBelongVertex = function isBelongVertex(r, g, b, a) { 117 | return Math.abs(vertexColor[0] - r) <= 10 && Math.abs(vertexColor[1] - g) <= 10 && Math.abs(vertexColor[2] - b) <= 10; 118 | }; 119 | 120 | // 判断是否属于底色 121 | var isBelongBackground = function isBelongBackground(r, g, b, a) { 122 | return 255 - r <= 20 && 255 - g <= 20 && 255 - b <= 20 && 255 - a <= 20; 123 | }; 124 | 125 | // 扫描像素 126 | for (var i = startIndex; i < endIndex; i += 4) { 127 | var r = data[i], 128 | g = data[i + 1], 129 | b = data[i + 2], 130 | a = data[i + 3]; 131 | // 过滤白色/透明/接近白色 132 | if (a === 0 || isBelongBackground(r, g, b, a)) { 133 | continue; 134 | } 135 | var rgba = "rgba(" + r + "," + g + "," + b + "," + a + ")"; 136 | if (colors.has(rgba) === true) { 137 | var color = colors.get(rgba); 138 | color[4]++; 139 | } else { 140 | colors.set(rgba, [r, g, b, a, 1]); 141 | } 142 | } 143 | 144 | // 颜色最多的是线段的颜色 145 | var _iteratorNormalCompletion = true; 146 | var _didIteratorError = false; 147 | var _iteratorError = undefined; 148 | 149 | try { 150 | for (var _iterator = colors.values()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 151 | var _color = _step.value; 152 | 153 | var countA = _color[4], 154 | countB = lineColor[4]; 155 | if (countA > countB) lineColor = _color; 156 | } 157 | 158 | // 颜色第二多的是点的颜色 159 | } catch (err) { 160 | _didIteratorError = true; 161 | _iteratorError = err; 162 | } finally { 163 | try { 164 | if (!_iteratorNormalCompletion && _iterator.return) { 165 | _iterator.return(); 166 | } 167 | } finally { 168 | if (_didIteratorError) { 169 | throw _iteratorError; 170 | } 171 | } 172 | } 173 | 174 | var _iteratorNormalCompletion2 = true; 175 | var _didIteratorError2 = false; 176 | var _iteratorError2 = undefined; 177 | 178 | try { 179 | for (var _iterator2 = colors.values()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { 180 | var _color2 = _step2.value; 181 | var _ref2 = [_color2[0], _color2[1], _color2[2], _color2[3]], 182 | _r3 = _ref2[0], 183 | _g3 = _ref2[1], 184 | _b3 = _ref2[2], 185 | _a3 = _ref2[3]; 186 | 187 | if (isBelongLine(_r3, _g3, _b3, _a3)) { 188 | continue; 189 | } 190 | var _countA = _color2[4], 191 | _countB = vertexColor[4]; 192 | if (_countA > _countB) vertexColor = _color2; 193 | } 194 | 195 | /* 196 | @ 收集图像中的端点 197 | @ 为了方便处理,把端点视作矩形(rect) 198 | */ 199 | } catch (err) { 200 | _didIteratorError2 = true; 201 | _iteratorError2 = err; 202 | } finally { 203 | try { 204 | if (!_iteratorNormalCompletion2 && _iterator2.return) { 205 | _iterator2.return(); 206 | } 207 | } finally { 208 | if (_didIteratorError2) { 209 | throw _iteratorError2; 210 | } 211 | } 212 | } 213 | 214 | var vertexes = []; 215 | var collect = function collect(index) { 216 | // 端点的边界 --- 用一个 rect 表示 217 | var top = index / (width * 4) + 1 >> 0, 218 | right = index % (width * 4) / 4 >> 0, 219 | bottom = top, 220 | left = right; 221 | // RGBA 222 | var r = data[index], 223 | g = data[index + 1], 224 | b = data[index + 2], 225 | a = data[index + 3]; 226 | while (isBelongVertex(r, g, b, a)) { 227 | // 删除水平方向的点 228 | var boundary = clearHorizontal(index); 229 | left = Math.min(left, boundary.left); 230 | right = Math.max(right, boundary.right); 231 | // 点往下移动 232 | index += width * 4; 233 | r = data[index], g = data[index + 1], b = data[index + 2], a = data[index + 3]; 234 | // bottom 像素加1 235 | ++bottom; 236 | } 237 | // 将点存入 vertexes 238 | vertexes.push({ top: top, right: right, bottom: bottom, left: left }); 239 | }; 240 | 241 | /* 242 | @ 清空指定点左右同色的像素 243 | */ 244 | var clearHorizontal = function clearHorizontal(index) { 245 | // 左坐标 246 | var leftIndex = index - 4; 247 | // 向左 248 | while (isBelongVertex(data[leftIndex], data[leftIndex + 1], data[leftIndex + 2], data[leftIndex + 3])) { 249 | // 把 a 设置为 0 表示清除 250 | data[leftIndex + 3] = 0; 251 | // 向左移动 252 | leftIndex -= 4; 253 | } 254 | // 右坐标 255 | var rightIndex = index; 256 | // 向右 257 | while (isBelongVertex(data[rightIndex], data[rightIndex + 1], data[rightIndex + 2], data[rightIndex + 3])) { 258 | // 把 a 设置为 0 表示清除 259 | data[rightIndex + 3] = 0; 260 | // 向左移动 261 | rightIndex -= 4; 262 | } 263 | 264 | var left = leftIndex % (width * 4) / 4 >> 0, 265 | right = rightIndex % (width * 4) / 4 >> 0; 266 | return { left: left, right: right }; 267 | }; 268 | 269 | // 矩形相交 270 | var isRectCross = function isRectCross(rectA, rectB) { 271 | var topA = rectA.top, 272 | rightA = rectA.right, 273 | bottomA = rectA.bottom, 274 | leftA = rectA.left; 275 | var topB = rectB.top, 276 | rightB = rectB.right, 277 | bottomB = rectB.bottom, 278 | leftB = rectB.left; 279 | // 判断垂直方向是否具备相交条件 280 | 281 | if (topA <= topB && bottomA >= topB || topB <= topA && bottomB >= topA) { 282 | // 判断水平方向是否具备相交条件 283 | if (leftA <= leftB && rightA >= leftB || leftB <= leftA && rightB >= leftA) { 284 | return true; 285 | } 286 | } 287 | return false; 288 | }; 289 | 290 | // 合并矩形 291 | var mergeRect = function mergeRect(rectA, rectB) { 292 | return { 293 | top: Math.min(rectA.top, rectB.top), 294 | right: Math.max(rectA.right, rectB.right), 295 | bottom: Math.max(rectA.bottom, rectB.bottom), 296 | left: Math.min(rectA.left, rectB.left) 297 | }; 298 | }; 299 | 300 | // 扫描图像 301 | for (var _i = startIndex; _i < endIndex; _i += 4) { 302 | var _r = data[_i], 303 | _g = data[_i + 1], 304 | _b = data[_i + 2], 305 | _a = data[_i + 3]; 306 | // 过滤白色/透明/接近白色 307 | if (_a === 0 || isBelongBackground(_r, _g, _b, _a)) { 308 | continue; 309 | } 310 | // 遇到端点 311 | else if (isBelongVertex(_r, _g, _b, _a)) { 312 | // 收集端点 313 | collect(_i); 314 | } 315 | } 316 | 317 | // 由于噪点的影响 vertexes 并不精准,需要进行一次归并 318 | for (var _i2 = 0; _i2 < vertexes.length - 1; ++_i2) { 319 | var rectA = vertexes[_i2]; 320 | // 跳过被删除的节点 321 | if (!rectA) continue; 322 | for (var j = 0; j < vertexes.length; ++j) { 323 | var rectB = vertexes[j]; 324 | // 跳过被删除的节点 325 | if (_i2 === j || !rectB) continue; 326 | // 矩形相交 327 | if (isRectCross(rectA, rectB) === true) { 328 | // 合并矩形 329 | rectA = vertexes[_i2] = mergeRect(rectA, rectB); 330 | // 删除 rectB 331 | delete vertexes[j]; 332 | } 333 | } 334 | } 335 | 336 | // 端点的中心坐标 337 | var coords = []; 338 | // 端点的半径 339 | var radius = 0; 340 | // 过滤空洞 341 | vertexes.forEach(function (rect) { 342 | var w = rect.right - rect.left, 343 | h = rect.bottom - rect.top; 344 | // 半径取最大值 345 | radius = Math.max(radius, w / 2, h / 2) >> 0; 346 | coords.push([rect.left + w / 2 >> 0, rect.top + h / 2 >> 0]); 347 | }); 348 | 349 | // 最终的线段 350 | var lines = []; 351 | 352 | /* 353 | @ 扫描两点之间是否存在线段 354 | @ 思路:均分断点 355 | @ AB间均分为 n 段,此时 A ---> B 之间均匀分布着 n - 1 个点 356 | @ 校验这n个点是否属于线段 357 | */ 358 | 359 | for (var _i3 = 0, len = coords.length; _i3 < len - 1; ++_i3) { 360 | var aX = coords[_i3][0], 361 | aY = coords[_i3][1]; 362 | for (var _j = _i3 + 1; _j < len; ++_j) { 363 | var bX = coords[_j][0], 364 | bY = coords[_j][1]; 365 | // AB 的距离 366 | var distance = Math.sqrt(Math.pow(aX - bX, 2) + Math.pow(aY - bY, 2)); 367 | // AB 均分为 n 个子线段,每个子线段的长度不得大于端点的直径,避免漏扫描 368 | var n = distance / (2 * radius) >> 0; 369 | // 子线段的步长(分X与Y) 370 | var stepX = (bX - aX) / n, 371 | stepY = (bY - aY) / n; 372 | while (--n > 0) { 373 | var index = (aX + stepX * n >> 0) * 4 + (aY + stepY * n >> 0) * width * 4; 374 | var _ref = [data[index], data[index + 1], data[index + 2], data[index + 3]], 375 | _r2 = _ref[0], 376 | _g2 = _ref[1], 377 | _b2 = _ref[2], 378 | _a2 = _ref[3]; 379 | // 断点没有落在线段上 380 | 381 | if (!isBelongLine(_r2, _g2, _b2, _a2)) break; 382 | } 383 | 384 | // 被检验的点都在线段上,表示 AB 成线 385 | if (0 === n) { 386 | // 还原尺寸 387 | lines.push({ 388 | x1: coords[_i3][0] * resolution, 389 | y1: coords[_i3][1] * resolution, 390 | x2: coords[_j][0] * resolution, 391 | y2: coords[_j][1] * resolution 392 | }); 393 | } 394 | } 395 | } 396 | 397 | // 删除对象 398 | canvas = colors = imageData = data = null; 399 | // return {vertexColor, lineColor, lines}; 400 | // 端点颜色 401 | var baseVertexColor = vertexColor[0] * Math.pow(256, 2) + vertexColor[1] * Math.pow(256, 1) + vertexColor[2]; 402 | // 线段颜色 403 | var baseLineColor = lineColor[0] * Math.pow(256, 2) + lineColor[1] * Math.pow(256, 1) + lineColor[2]; 404 | // 手绘线的颜色取端点的半色值 405 | var strokeColor = vertexColor[0] * Math.pow(256, 2) / 2 + vertexColor[1] * Math.pow(256, 1) / 2 + vertexColor[2] / 2; 406 | 407 | return { 408 | lineColor: baseLineColor, 409 | vertexColor: baseVertexColor, 410 | strokeColor: strokeColor, 411 | activeVertexColor: baseVertexColor, 412 | lines: lines 413 | }; 414 | } 415 | }]); 416 | 417 | return OneStrokePlugin; 418 | }(); 419 | 420 | exports.default = OneStrokePlugin; 421 | 422 | },{}],2:[function(require,module,exports){ 423 | "use strict"; 424 | 425 | var _OneStrokePlugin = require("./OneStrokePlugin.es6"); 426 | 427 | var _OneStrokePlugin2 = _interopRequireDefault(_OneStrokePlugin); 428 | 429 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 430 | 431 | window.OneStrokePlugin = _OneStrokePlugin2.default; 432 | 433 | },{"./OneStrokePlugin.es6":1}]},{},[2]); 434 | -------------------------------------------------------------------------------- /src/script/onestroke.es6: -------------------------------------------------------------------------------- 1 | // 向前兼容 2 | import 'babel-polyfill'; 3 | 4 | // PIXI 工具 5 | import './lib/utils.es6'; 6 | 7 | // 识图插件 8 | import oneStrokePlugin from './oneStrokePlugin.es6'; 9 | 10 | // 事件 11 | import Events from './lib/Events'; 12 | 13 | // 一笔画 14 | class OneStroke { 15 | constructor(config) { 16 | const app = new PIXI.Application( 17 | { 18 | width: 375, 19 | height: 603, 20 | resolution: 2, 21 | antialias: true, 22 | transparent: true, 23 | view: document.getElementById("easel") 24 | } 25 | ); 26 | 27 | [this.app, this.stage, this.renderer, this.view] = [app, app.stage, app.renderer, app.renderer.view]; 28 | 29 | // config 挂载到 this 30 | this.config = config; 31 | 32 | // 当前关数 33 | this.curLevel = 0; 34 | 35 | // 总关数 36 | this.total = config.levels.length; 37 | 38 | // 当前线段 39 | this.lines = []; 40 | 41 | // 底图线段 42 | this.baseLines = []; 43 | 44 | // 手绘的线段 45 | this.strokes = []; 46 | 47 | // 回收站 ----- 用于存储待回退的线段与坐标 48 | this.recycler = []; 49 | 50 | // 底图端点 51 | this.baseVertexes = []; 52 | 53 | // 当前的端点坐标集合 54 | this.coords = []; 55 | 56 | // 当前有效的可连接点 57 | this.curValidCoords = []; 58 | 59 | // 已连接的线段快照 ------ 用于快速查找两点之间是否已经连接过 60 | this.strokedSnap = {}; 61 | 62 | // 默认的线段颜色 63 | this.lineColor = config.lineColor; 64 | 65 | // 默认的端点颜色 66 | this.vertexColor = config.vertexColor; 67 | 68 | // 激活的端点颜色 69 | this.activeVertexColor = config.activeVertexColor 70 | 71 | // 绘制中的线段颜色 72 | this.strokeColor = config.strokeColor; 73 | 74 | // 端点半径 75 | this.vertexRadius = 9; 76 | 77 | // 线段的厚度 78 | this.lineWidth = 10; 79 | 80 | // 当前点 81 | this.curVertex = null; 82 | 83 | // 当前坐标信息 84 | this.curCoord = null; 85 | 86 | // 当前线段 87 | this.curStroke = null; 88 | 89 | // 当前的有效点 90 | this.validCoords = []; 91 | 92 | // 当前屏幕width 与 ip6.width 的比例 93 | this.ratio = 375 / document.body.clientWidth; 94 | 95 | // view 左边界距离视窗左边界的距离 96 | this.viewLeft = 0; 97 | 98 | // 当前手指信息 99 | this.finger = {}; 100 | 101 | // 默认不可以绘制 102 | this.canStroke = false; 103 | 104 | // 实例插件 105 | this.plugin = new oneStrokePlugin(); 106 | 107 | // 事件绑定 108 | this.event = new Events(); 109 | 110 | // 事件绑定 this 111 | this.touchstartHandle = this.touchstartHandle.bind(this); 112 | this.touchmoveHandle = this.touchmoveHandle.bind(this); 113 | this.touchendHandle = this.touchendHandle.bind(this); 114 | this.touchcancelHandle = this.touchendHandle; 115 | 116 | // 兼容非移动端 117 | if("ontouchstart" in document) { 118 | this.touchstart = "touchstart"; 119 | this.touchmove = "touchmove"; 120 | this.touchend = "touchend"; 121 | this.touchcancel = "touchcancel"; 122 | } 123 | // 没有 touch 事件 124 | else { 125 | this.touchstart = "mousedown"; 126 | this.touchmove = "mousemove"; 127 | this.touchend = "mouseup"; 128 | // 并没有 mousecancel 129 | this.touchcancel = "mousecancle"; 130 | } 131 | // 初始化 132 | this.init(); 133 | } 134 | 135 | // 初始化 136 | init() { 137 | // 添加手指事件 138 | this.view.addEventListener(this.touchstart, this.touchstartHandle); 139 | this.view.addEventListener(this.touchmove, this.touchmoveHandle); 140 | this.view.addEventListener(this.touchend, this.touchendHandle); 141 | this.view.addEventListener(this.touchcancel, this.touchendHandle); 142 | } 143 | 144 | // 开始游戏 145 | start() { 146 | // 默认进入第一关 147 | this.enter(0); 148 | } 149 | 150 | // 重新开始 151 | restart() { 152 | // 重新进入当前关卡 153 | this.enter(this.curLevel); 154 | } 155 | 156 | // 销毁 157 | destory() { 158 | this.view.removeEventListener("touchstart", this.touchstartHandle); 159 | this.view.removeEventListener("touchmove", this.touchmoveHandle); 160 | this.view.removeEventListener("touchend", this.touchendHandle); 161 | this.view.removeEventListener("touchcancel", this.touchendHandle); 162 | // 清空动画与节点 163 | this.clean(); 164 | } 165 | 166 | // 暂停 167 | pause() { 168 | this.app.ticker.stop(); 169 | TweenMax.pauseAll(); 170 | } 171 | // 恢复 172 | resume() { 173 | this.app.ticker.start(); 174 | TweenMax.resumeAll(); 175 | } 176 | 177 | // gameover 178 | gameover() { 179 | this.event.dispatch("gameover"); 180 | } 181 | 182 | // 清空 183 | clean() { 184 | // 清空节点 185 | this.stage.removeChildren(); 186 | // 清空坐标组 187 | this.coords.length = 0; 188 | // 清除当前端点与线段 189 | this.curStroke = this.curVertex = this.curCoord = null; 190 | // 清空快照 191 | this.strokedSnap = {}; 192 | // 清空手绘线 193 | this.strokes.length = 0; 194 | // 清空回收站 195 | this.recycler.length = 0; 196 | // 清空动画 197 | TweenMax.killAll(); 198 | // 解除锁定 199 | this.lock = false; 200 | // 默认不可以绘制 201 | this.canStroke = false; 202 | } 203 | 204 | // 进入对应的关卡 205 | enter(index) { 206 | // 清空当前关卡的图形 207 | this.clean(); 208 | let curLevel = this.config.levels[index]; 209 | 210 | // 当前关卡数 211 | this.curLevel = index; 212 | 213 | // 当前是图片路径 214 | if(curLevel.lines === undefined && curLevel.src != undefined) { 215 | // 通知外部关卡载入中 216 | this.event.dispatch("level-loading"); 217 | let name = curLevel.name; 218 | this.plugin.parse(curLevel.src) 219 | .then(curLevel => { 220 | curLevel.name = name; 221 | this.event.dispatch("level-loaded"); 222 | this.drawLevel(curLevel); 223 | }) 224 | .catch(err => console.log("图片载入失败")) 225 | } 226 | // 当前是关卡对象 227 | else { 228 | this.drawLevel(curLevel); 229 | } 230 | 231 | } 232 | 233 | // 绘制当前关卡 234 | drawLevel(curLevel) { 235 | // 当前线段 ---- 拷贝config中的信息 236 | this.lines = curLevel.lines.map(item => { 237 | let newItem = Object.create(item); 238 | return newItem; 239 | }); 240 | 241 | // 底图端点的颜色 242 | this.vertexColor = curLevel.vertexColor || config.vertexColor; 243 | 244 | // 手绘线段的颜色 245 | this.strokeColor = curLevel.strokeColor || this.strokeColor; 246 | 247 | // 激活点的颜色 248 | this.activeVertexColor = curLevel.activeVertexColor || this.activeVertexColor; 249 | 250 | // PIXI 的分辨率 251 | let resolution = this.renderer.resolution; 252 | 253 | // 收集当前端点 254 | this.lines.forEach((item) => { 255 | ["x1", "y1", "x2", "y2"].forEach((key) => item[key] = item[key] / resolution); 256 | let {x1, y1, x2, y2} = item; 257 | this.addCoords({x: x1, y: y1}, {x: x2, y: y2}); 258 | }); 259 | 260 | // 找出坐标对应的有效连接点 261 | this.findValidCoords(); 262 | 263 | // 绘制底图线段 264 | this.drawBaseLines(); 265 | 266 | // 绘制底图端点 267 | this.drawBaseVertexes(); 268 | 269 | // 更新当前有效点(坐标) 270 | this.updateValidCoords(); 271 | 272 | // 通知游戏开始 273 | this.event.dispatch("start", curLevel); 274 | } 275 | 276 | // 向 coords 添加端点 277 | addCoords(...coords) { 278 | coords.forEach(({x, y}) => { 279 | for(let i = 0, len = this.coords.length; i < len; ++i) { 280 | if(this.coords[i].x === x && this.coords[i].y === y) { 281 | return false; 282 | } 283 | } 284 | this.coords.push({x, y}) 285 | }); 286 | } 287 | 288 | // 绘制底图线段 289 | drawBaseLines() { 290 | this.baseLines = this.lines.map( 291 | ({x1, y1, x2, y2}) => { 292 | let line = new PIXI.Graphics() 293 | .lineStyle(this.lineWidth, this.lineColor, 1) 294 | .moveTo(x1, y1) 295 | .lineTo(x2, y2) 296 | .closePath(); 297 | this.stage.addChild(line); 298 | return line; 299 | } 300 | ); 301 | } 302 | 303 | // 绘制底图端点 304 | drawBaseVertexes() { 305 | this.baseVertexes = this.coords.map( 306 | ({x, y}) => { 307 | let vertex = new PIXI.Graphics() 308 | .beginFill(this.vertexColor, 1) 309 | .drawCircle(0, 0, this.vertexRadius); 310 | 311 | vertex.set({x: x, y: y}); 312 | this.stage.addChild(vertex); 313 | return vertex; 314 | } 315 | ); 316 | } 317 | 318 | // touchstart 319 | touchstartHandle(e) { 320 | if(this.lock === true) return ; 321 | // 移动端 322 | if(this.touchstart === "touchstart") { 323 | var {pageX: x, pageY: y} = e.targetTouches[0]; 324 | } 325 | // 非移动端 326 | else { 327 | var {clientX: x, clientY: y} = e; 328 | } 329 | 330 | // 修正 x 331 | x -= this.viewLeft; 332 | 333 | x *= this.ratio; 334 | y *= this.ratio; 335 | this.finger.x = x, this.finger.y = y; 336 | // 表示图形画了一半,继续画 337 | if(this.curStroke !== null) { 338 | this.updateLine(x, y); 339 | this.canStroke = true; 340 | } 341 | // 表示图形第一次绘制 342 | else { 343 | let coord = this.check(x, y); 344 | // 手指下没有端点 345 | if(coord === false) this.canStroke = false; 346 | // 手指下有端点 347 | else { 348 | this.canStroke = true; 349 | // 生成新位置的激活点 350 | this.addActiveVertex(coord); 351 | // 创建一条长度为0的手绘线段 352 | this.generateStroke(coord); 353 | } 354 | } 355 | } 356 | // touchmove 357 | touchmoveHandle(e) { 358 | // 不能画线 359 | if(this.canStroke === false || this.lock === true) return ; 360 | // 移动端 361 | if(this.touchstart === "touchstart") { 362 | var {pageX: x, pageY: y} = e.targetTouches[0]; 363 | } 364 | // 非移动端 365 | else { 366 | var {clientX: x, clientY: y} = e; 367 | } 368 | // 修正 x 369 | x -= this.viewLeft; 370 | 371 | x *= this.ratio; 372 | y *= this.ratio; 373 | this.updateLine(x, y); 374 | } 375 | // touchend 376 | touchendHandle(e) { 377 | // 不能画线 378 | if(this.canStroke === false || this.lock === true) return ; 379 | // 没有成形的手绘线 380 | if(this.strokes.length === 1) { 381 | // 移除当前激活点 382 | this.removeActiveVertex(); 383 | // 删除当前 stroke 384 | this.stage.removeChild(this.curStroke); 385 | this.curStroke = null; 386 | // strokes 清零 387 | this.strokes.length = 0; 388 | // recycler 清空 389 | this.recycler.length = 0; 390 | // 更新有效点 391 | this.updateValidCoords(); 392 | } 393 | // 有成形的手绘线 ---- 将未成形的线段回退到起始点 394 | else { 395 | let points = this.curStroke.graphicsData[0].shape.points; 396 | points[2] = points[0]; 397 | points[3] = points[1]; 398 | this.curStroke.dirty++ & this.curStroke.clearDirty++; 399 | } 400 | // 重置为不可绘制 401 | this.canStroke = false; 402 | } 403 | 404 | // 找出坐标对应的有效连接点 405 | findValidCoords() { 406 | this.coords.forEach(coord => { 407 | // 创建一个有效坐标数组 408 | coord.validCoords = []; 409 | this.lines.forEach(({x1, y1, x2, y2}) => { 410 | // 坐标是当前线段的起点 411 | if(coord.x === x1 && coord.y === y1) { 412 | coord.validCoords.push(this.findCoord(x2, y2)); 413 | } 414 | // 坐标是当前线段的终点 415 | else if(coord.x === x2 && coord.y === y2) { 416 | coord.validCoords.push(this.findCoord(x1, y1)); 417 | } 418 | }) 419 | }); 420 | } 421 | 422 | // 返回对应的坐标点 423 | findCoord(x, y) { 424 | for(let coord of this.coords) { 425 | if(coord.x === x && coord.y === y) return coord; 426 | } 427 | return false; 428 | } 429 | 430 | // 更新当前的有效点 431 | updateValidCoords(coord) { 432 | // 默认是当前所有坐标 433 | if(coord === undefined) { 434 | this.validCoords = this.coords; 435 | } 436 | // 剔除 coord.validCoords 中无效的坐标 437 | else { 438 | for(let i = 0; i < coord.validCoords.length; ++i) { 439 | let validCoord = coord.validCoords[i]; 440 | let snapKey = "stroke_from_x_" + validCoord.x + "_y_" + validCoord.y + "_to_x_" + coord.x + "_y_" + coord.y; 441 | // 标记当前点与当前有效点已经连线 442 | if(this.strokedSnap[snapKey] === true) { 443 | coord.validCoords[i].connected = true; 444 | } 445 | // 标记未链接 446 | else { 447 | coord.validCoords[i].connected = false; 448 | } 449 | } 450 | this.validCoords = coord.validCoords; 451 | } 452 | this.validCoords = coord !== undefined ? coord.validCoords : this.coords; 453 | } 454 | 455 | // 更新当前线段 456 | updateLine(x = this.finger.x, y = this.finger.y) { 457 | let coord = this.check(x, y), 458 | points = this.curStroke.graphicsData[0].shape.points; 459 | // 手指下不存在有效点 460 | if(coord === false) { 461 | points[2] = x; 462 | points[3] = y; 463 | } 464 | // 手批下是有效点 465 | else if(coord !== this.curCoord){ 466 | // 两点成线 467 | points[2] = coord.x; 468 | points[3] = coord.y; 469 | // 从 this.lines 中删除这条线段 470 | for(let i = 0, len = this.lines.length; i < len; ++i) { 471 | let {x1, y1, x2, y2, isDeleted} = this.lines[i]; 472 | // 跳过已经标记删除的线段 473 | if(isDeleted === true) continue; 474 | // 手指下的当前点 475 | let {x: x3, y: y3} = coord; 476 | // 当前线段的起始点 477 | let {x: x4, y: y4} = this.curCoord; 478 | if( 479 | x1 === x3 && y1 === y3 && x2 === x4 && y2 === y4 480 | || 481 | x1 === x4 && y1 === y4 && x2 === x3 && y2 === y3 482 | ) { 483 | // 标记删除 484 | this.lines[i].isDeleted = true; 485 | // 把线段与坐标回收 486 | this.recycler[this.recycler.length] = {line: this.lines[i], curCoord: this.curCoord}; 487 | // this.lines.splice(i, 1); 488 | break; 489 | } 490 | } 491 | 492 | // 未通关,生成新的线段 493 | if(this.lines.length > this.strokes.length) { 494 | // 将已经完成的线段存入快照 495 | let snapKeyA = "stroke_from_x_" + this.curCoord.x + "_y_" + this.curCoord.y + "_to_x_" + coord.x + "_y_" + coord.y; 496 | let snapKeyB = "stroke_from_x_" + coord.x + "_y_" + coord.y + "_to_x_" + this.curCoord.x + "_y_" + this.curCoord.y; 497 | this.strokedSnap[snapKeyA] = this.strokedSnap[snapKeyB] = true; 498 | // 将删除的线段 499 | // 删除上一次的激活点 500 | this.removeActiveVertex(); 501 | // 生成新位置的激活点 502 | this.addActiveVertex(coord); 503 | // 创建新线段后 curStroke 指向会变,所以提前更新 504 | this.curStroke.dirty++ & this.curStroke.clearDirty ++; 505 | // 创建一条长度为0的手绘线段 506 | this.generateStroke(coord); 507 | } 508 | // 通关 ----- 手绘线的数量与待画线相等 509 | else { 510 | // 删除上一次的激活点 511 | this.removeActiveVertex(); 512 | // 锁定 513 | this.lock = true; 514 | this.pass(); 515 | } 516 | } 517 | this.curStroke.dirty++ & this.curStroke.clearDirty ++; 518 | } 519 | 520 | // 创建一条长度为0的手绘线段 521 | generateStroke(coord) { 522 | let {x, y} = coord; 523 | this.curStroke = new PIXI.Graphics() 524 | .lineStyle(this.lineWidth, this.strokeColor, 1) 525 | .moveTo(x, y) 526 | .lineTo(x, y) 527 | .closePath(); 528 | this.strokes.push(this.curStroke); 529 | // 添加到舞台 530 | this.stage.addChild(this.curStroke); 531 | // 设置层级 532 | this.stage.setChildIndex(this.curStroke, this.baseLines.length); 533 | // 更新有效连接点 534 | this.updateValidCoords(coord); 535 | } 536 | 537 | // 监测手指下是否有端点 538 | check(x0, y0) { 539 | for(let i = 0, len = this.validCoords.length; i < len; ++i) { 540 | let {x, y, connected} = this.validCoords[i]; 541 | // 跳过已连结的端点 542 | if(connected === true) continue; 543 | let distance = Math.sqrt(Math.pow(x - x0, 2) + Math.pow(y - y0, 2)); 544 | // 手指在半径内 ------ 移动端适当把半径放大 545 | if(distance <= this.vertexRadius * 1.5) { 546 | return this.validCoords[i]; 547 | } 548 | } 549 | return false; 550 | } 551 | // 激活的点 ----- 鼠标点击时的动画或关卡过关时的动画 552 | addActiveVertex(coord) { 553 | let vertex = new PIXI.Graphics() 554 | .beginFill(this.activeVertexColor, 1) 555 | .drawCircle(0, 0, this.vertexRadius); 556 | let {x, y} = coord; 557 | vertex.set({x: x, y: y}); 558 | // 添加到舞台 559 | this.stage.addChild(vertex); 560 | // 当前端点 561 | this.curVertex = vertex; 562 | // 当前坐标信息 563 | this.curCoord = coord; 564 | // 添加动画 565 | TweenMax.to(vertex, .2, {alpha: .4, scaleX: 1.6, scaleY: 1.6, yoyo: true, repeat: -1}); 566 | } 567 | // 移除激活点 568 | removeActiveVertex() { 569 | this.stage.removeChild(this.curVertex); 570 | TweenMax.killTweensOf(this.curVertex); 571 | } 572 | // 后退一步 573 | rollback() { 574 | // 回收器有东西并且不止一条记录 575 | if(this.recycler.length > 1) { 576 | let recyclerInfo = this.recycler.pop(); 577 | // 删除快照 578 | let snapKeyA = "stroke_from_x_" + this.curCoord.x + "_y_" + this.curCoord.y + "_to_x_" + recyclerInfo.curCoord.x + "_y_" + recyclerInfo.curCoord.y; 579 | let snapKeyB = "stroke_from_x_" + recyclerInfo.curCoord.x + "_y_" + recyclerInfo.curCoord.y + "_to_x_" + this.curCoord.x + "_y_" + this.curCoord.y; 580 | this.strokedSnap[snapKeyA] = this.strokedSnap[snapKeyB] = false; 581 | // 移除当前激活点 582 | this.removeActiveVertex(); 583 | // 重新设置激活点 584 | this.addActiveVertex(recyclerInfo.curCoord); 585 | // 标记线段未删除 586 | recyclerInfo.line.isDeleted = false; 587 | 588 | // 移除当前的手绘线 589 | let lastStroke = this.strokes.pop(); 590 | this.stage.removeChild(lastStroke); 591 | // 重新指定当前手绘线 592 | this.curStroke = this.strokes[this.strokes.length - 1]; 593 | // 线段回退到原点 594 | let points = this.curStroke.graphicsData[0].shape.points; 595 | points[2] = points[0]; 596 | points[3] = points[1]; 597 | this.curStroke.dirty++ & this.curStroke.clearDirty++; 598 | // 更新有效连接坐标 599 | this.updateValidCoords(this.curCoord); 600 | } 601 | // 回收器里只有一条记录 602 | else if(this.recycler.length === 1) { 603 | // 直接调用重新开始游戏 604 | this.restart(); 605 | } 606 | } 607 | // 通关 608 | pass() { 609 | // 清除所有的底图基点 610 | this.baseVertexes.forEach(vertex => TweenMax.to(vertex, 1, {scaleX: 2.5, scaleY: 2.5, alpha: 0})); 611 | // 清空所有的底图线段 612 | this.baseLines.forEach(line => this.stage.removeChild(line)); 613 | // 敲落手绘线 614 | this.knockStrokes(() => this.event.dispatch("pass")); 615 | } 616 | // 下一关 617 | next() { 618 | console.log(this.curLevel, this.config.levels.length - 1); 619 | if(this.curLevel < this.config.levels.length - 1) { 620 | this.enter(this.curLevel + 1); 621 | } 622 | else { 623 | this.gameover(); 624 | } 625 | } 626 | // 上一关 627 | prev() { 628 | if(this.curLevel > 0) { 629 | this.enter(this.curLevel - 1); 630 | } 631 | } 632 | // 敲落手绘线段 633 | knockStrokes(cb) { 634 | // promises 635 | let promises = this.strokes.map(stroke => new Promise( 636 | (resolve, reject) => { 637 | // 设置中心点 638 | let {width, height, left, top} = stroke.getBounds(); 639 | stroke.set({pivotX: left + width / 2, pivotY: top + height / 2, x: stroke.x + left + width / 2, y: stroke.y + top + height / 2}); 640 | TweenMax.to(stroke, Math.random() * .8 + 1.5, { 641 | delay: Math.random() * .5, 642 | rotation: Math.PI, 643 | y: this.view.height * 1.5, 644 | onComplete: () => resolve() 645 | } 646 | ) 647 | } 648 | )); 649 | Promise.race(promises).then(() => cb()) 650 | } 651 | } 652 | 653 | window.OneStroke = OneStroke; 654 | // 屏蔽pixijs信息 655 | PIXI.utils.skipHello(); -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/RaphaelPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 0.2.2 3 | * DATE: 2017-01-17 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | */ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | var _NaNExp = /[^\d\-\.]/g, 18 | _DEG2RAD = Math.PI / 180, 19 | _numExp = /(\d|\.)+/g, 20 | _colorLookup = {aqua:[0,255,255], 21 | lime:[0,255,0], 22 | silver:[192,192,192], 23 | black:[0,0,0], 24 | maroon:[128,0,0], 25 | teal:[0,128,128], 26 | blue:[0,0,255], 27 | navy:[0,0,128], 28 | white:[255,255,255], 29 | fuchsia:[255,0,255], 30 | olive:[128,128,0], 31 | yellow:[255,255,0], 32 | orange:[255,165,0], 33 | gray:[128,128,128], 34 | purple:[128,0,128], 35 | green:[0,128,0], 36 | red:[255,0,0], 37 | pink:[255,192,203], 38 | cyan:[0,255,255], 39 | transparent:[255,255,255,0]}, 40 | //parses a color (like #9F0, #FF9900, or rgb(255,51,153)) into an array with 3 elements for red, green, and blue. Also handles rgba() values (splits into array of 4 elements of course) 41 | _parseColor = function(color) { 42 | if (typeof(color) === "number") { 43 | return [color >> 16, (color >> 8) & 255, color & 255]; 44 | } else if (color === "" || color == null || color === "none" || typeof(color) !== "string") { 45 | return _colorLookup.transparent; 46 | } else if (_colorLookup[color]) { 47 | return _colorLookup[color]; 48 | } else if (color.charAt(0) === "#") { 49 | if (color.length === 4) { //for shorthand like #9F0 50 | color = "#" + color.charAt(1) + color.charAt(1) + color.charAt(2) + color.charAt(2) + color.charAt(3) + color.charAt(3); 51 | } 52 | color = parseInt(color.substr(1), 16); 53 | return [color >> 16, (color >> 8) & 255, color & 255]; 54 | } 55 | return color.match(_numExp) || _colorLookup.transparent; 56 | }, 57 | 58 | _transformMap = {scaleX:1, scaleY:1, tx:1, ty:1, rotation:1, shortRotation:1, skewX:1, skewY:1, scale:1}, 59 | 60 | //parses the transform values for an element, returning an object with x, y, scaleX, scaleY, rotation, skewX, and skewY properties. Note: by default (for performance reasons), all skewing is combined into skewX and rotation but skewY still has a place in the transform object so that we can record how much of the skew is attributed to skewX vs skewY. Remember, a skewY of 10 looks the same as a rotation of 10 and skewX of -10. 61 | _getTransform = function(t, rec) { 62 | var s = t.matrix, 63 | min = 0.000001, 64 | a = s.a, 65 | b = s.b, 66 | c = s.c, 67 | d = s.d, 68 | m = rec ? t._gsTransform || {skewY:0} : {skewY:0}, 69 | invX = (m.scaleX < 0); //in order to interpret things properly, we need to know if the user applied a negative scaleX previously so that we can adjust the rotation and skewX accordingly. Otherwise, if we always interpret a flipped matrix as affecting scaleY and the user only wants to tween the scaleX on multiple sequential tweens, it would keep the negative scaleY without that being the user's intent. 70 | 71 | m.tx = s.e - (m.ox || 0); //ox is the offset x that we record in setRatio() whenever we apply a custom transform that might use a pivot point. Remember, s.e and s.f get affected by things like scale. For example, imagine an object whose top left corner is at 100,100 and then we scale it up to 300% using the center as the pivot point - that corner would now be very different even though to the user, they didn't intend to change/tween the x/y position per se. Therefore, we record whatever offsets we make so that we can compensate when reading the values back. 72 | m.ty = s.f - (m.oy || 0); //oy is the offset y (see note above) 73 | m.scaleX = Math.sqrt(a * a + b * b); 74 | m.scaleY = Math.sqrt(d * d + c * c); 75 | m.rotation = (a || b) ? Math.atan2(b, a) : m.rotation || 0; //note: if scaleX is 0, we cannot accurately measure rotation. Same for skewX with a scaleY of 0. Therefore, we default to the previously recorded value (or zero if that doesn't exist). 76 | m.skewX = (c || d) ? Math.atan2(c, d) + m.rotation : m.skewX || 0; 77 | if (Math.abs(m.skewX) > Math.PI / 2) { 78 | if (invX) { 79 | m.scaleX *= -1; 80 | m.skewX += (m.rotation <= 0) ? Math.PI : -Math.PI; 81 | m.rotation += (m.rotation <= 0) ? Math.PI : -Math.PI; 82 | } else { 83 | m.scaleY *= -1; 84 | m.skewX += (m.skewX <= 0) ? Math.PI : -Math.PI; 85 | } 86 | } 87 | //some browsers have a hard time with very small values like 2.4492935982947064e-16 (notice the "e-" towards the end) and would render the object slightly off. So we round to 0 in these cases. The conditional logic here is faster than calling Math.abs(). 88 | if (m.rotation < min) if (m.rotation > -min) if (a || b) { 89 | m.rotation = 0; 90 | } 91 | if (m.skewX < min) if (m.skewX > -min) if (b || c) { 92 | m.skewX = 0; 93 | } 94 | if (rec) { 95 | t._gsTransform = m; //record to the object's _gsTransform which we use so that tweens can control individual properties independently (we need all the properties to accurately recompose the matrix in the setRatio() method) 96 | } 97 | return m; 98 | }, 99 | 100 | //takes a value and a default number, checks if the value is relative, null, or numeric and spits back a normalized number accordingly. Primarily used in the _parseTransform() function. 101 | _parseVal = function(v, d) { 102 | return (v == null) ? d : (typeof(v) === "string" && v.indexOf("=") === 1) ? parseInt(v.charAt(0)+"1", 10) * Number(v.substr(2)) + d : Number(v); 103 | }, 104 | 105 | //translates strings like "40deg" or "40" or 40rad" or "+=40deg" to a numeric radian angle, optionally relative to a default value (if "+=" or "-=" prefix is found) 106 | _parseAngle = function(v, d) { 107 | var m = (v.indexOf("rad") === -1) ? _DEG2RAD : 1, 108 | r = (v.indexOf("=") === 1); 109 | v = Number(v.replace(_NaNExp, "")) * m; 110 | return r ? v + d : v; 111 | }, 112 | 113 | 114 | RaphaelPlugin = _gsScope._gsDefine.plugin({ 115 | propName: "raphael", 116 | version: "0.2.2", 117 | API: 2, 118 | 119 | //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 120 | init: function(target, value, tween) { 121 | if (!target.attr) { //raphael must have attr() method 122 | return false; 123 | } 124 | this._target = target; 125 | this._tween = tween; 126 | this._props = target._gsProps = target._gsProps || {}; 127 | var p, s, v, pt, clr1, clr2, rel; 128 | 129 | for (p in value) { 130 | 131 | v = value[p]; 132 | 133 | if (p === "transform") { 134 | this._parseTransform(target, v); 135 | continue; 136 | } else if (_transformMap[p] || p === "pivot") { 137 | this._parseTransform(target, value); 138 | continue; 139 | } 140 | 141 | s = target.attr(p); 142 | 143 | //Some of these properties are in place in order to conform with the standard PropTweens in TweenPlugins so that overwriting and roundProps occur properly. For example, f and r may seem unnecessary here, but they enable other functionality. 144 | //_next:* next linked list node [object] 145 | //t: * target [object] 146 | //p: * property (camelCase) [string] 147 | //s: * starting value [number] 148 | //c: * change value [number] 149 | //f: * is function [boolean] 150 | //n: * name (for overwriting) [string] 151 | //b: beginning value [string] 152 | //i: intermediate value [string] 153 | //e: ending value [string] 154 | //r: * round [boolean] 155 | //type: 0=normal, 1=color, 2=rgba, -1=non-tweening prop [number] 156 | this._firstPT = pt = {_next:this._firstPT, 157 | t:this._props, 158 | p:p, 159 | b:s, 160 | f:false, 161 | n:"raphael_" + p, 162 | r:false, 163 | type:0}; 164 | 165 | //color values must be split apart into their R, G, B (and sometimes alpha) values and tweened independently. 166 | if (p === "fill" || p === "stroke") { 167 | clr1 = _parseColor(s); 168 | clr2 = _parseColor(v); 169 | pt.e = v; 170 | pt.s = Number(clr1[0]); //red starting value 171 | pt.c = Number(clr2[0]) - pt.s; //red change 172 | pt.gs = Number(clr1[1]); //green starting value 173 | pt.gc = Number(clr2[1]) - pt.gs; //green change 174 | pt.bs = Number(clr1[2]); //blue starting value 175 | pt.bc = Number(clr2[2]) - pt.bs; //blue change 176 | if (clr1.length > 3 || clr2.length > 3) { //detect an rgba() value 177 | pt.as = (clr1.length < 4) ? 1 : Number(clr1[3]); 178 | pt.ac = ((clr2.length < 4) ? 1 : Number(clr2[3])) - pt.as; 179 | pt.type = 2; //2 = rgba() tween 180 | } else { 181 | pt.type = 1; //1 = color tween, -1 = no tween, just set the value at the end because there's no changes 182 | } 183 | 184 | } else { 185 | 186 | s = (typeof(s) === "string") ? parseFloat(s.replace(_NaNExp, "")) : Number(s); 187 | 188 | if (typeof(v) === "string") { 189 | rel = (v.charAt(1) === "="); 190 | v = parseFloat(v.replace(_NaNExp, "")); 191 | } else { 192 | rel = false; 193 | } 194 | 195 | pt.e = (v || v === 0) ? (rel ? v + s : v) : value[p]; //ensures that any += or -= prefixes are taken care of. 196 | 197 | if ((s || s === 0) && (v || v === 0) && (pt.c = (rel ? v : v - s))) { //faster than isNaN(). Also, we set pt.c (change) here because if it's 0, we'll just treat it like a non-tweening value. can't do (v !== start) because if it's a relative value and the CHANGE is identical to the START, the condition will fail unnecessarily. 198 | pt.s = s; 199 | } else { 200 | pt.type = -1; 201 | pt.i = value[p]; //intermediate value is typically the same as the end value. 202 | pt.s = pt.c = 0; 203 | } 204 | 205 | } 206 | 207 | this._overwriteProps.push("raphael_" + p); 208 | if (pt._next) { 209 | pt._next._prev = pt; 210 | } 211 | } 212 | 213 | return true; 214 | }, 215 | 216 | //called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.) 217 | set: function(v) { 218 | var pt = this._firstPT, val; 219 | 220 | while (pt) { 221 | val = pt.c * v + pt.s; 222 | if (pt.r) { 223 | val = Math.round(val); 224 | } 225 | if (!pt.type) { 226 | pt.t[pt.p] = val; 227 | } else if (pt.type === 1) { //rgb() 228 | pt.t[pt.p] = "rgb(" + (val >> 0) + ", " + ((pt.gs + (v * pt.gc)) >> 0) + ", " + ((pt.bs + (v * pt.bc)) >> 0) + ")"; 229 | } else if (pt.type === 2) { //rgba() 230 | pt.t[pt.p] = "rgba(" + (val >> 0) + ", " + ((pt.gs + (v * pt.gc)) >> 0) + ", " + ((pt.bs + (v * pt.bc)) >> 0) + ", " + (pt.as + (v * pt.ac)) + ")"; 231 | } else if (pt.type === -1) { //non-tweening 232 | pt.t[pt.p] = pt.i; 233 | } 234 | pt = pt._next; 235 | } 236 | 237 | this._target.attr(this._props); 238 | 239 | //apply transform values like x, y, scaleX, scaleY, rotation, skewX, or skewY. We do these after looping through all the PropTweens because those are where the changes are made to scaleX/scaleY/rotation/skewX/skewY/x/y. 240 | if (this._transform) { 241 | pt = this._transform; //to improve speed and reduce size, reuse the pt variable as an alias to the _transform property 242 | var ang = pt.rotation, 243 | skew = ang - pt.skewX, 244 | a = Math.cos(ang) * pt.scaleX, 245 | b = Math.sin(ang) * pt.scaleX, 246 | c = Math.sin(skew) * -pt.scaleY, 247 | d = Math.cos(skew) * pt.scaleY, 248 | min = 0.000001, 249 | pxl = this._pxl, 250 | pyl = this._pyl; 251 | 252 | //some browsers have a hard time with very small values like 2.4492935982947064e-16 (notice the "e-" towards the end) and would render the object slightly off. So we round to 0 in these cases for both b and c. The conditional logic here is faster than calling Math.abs(). 253 | if (b < min) if (b > -min) { 254 | b = 0; 255 | } 256 | if (c < min) if (c > -min) { 257 | c = 0; 258 | } 259 | pt.ox = this._pxg - (pxl * a + pyl * c); //we must record the offset x/y that we're making from the regular tx/ty (matrix.e and f) so that we can correctly interpret positional data in _getTransform(). See note there on tx and ox. 260 | pt.oy = this._pyg - (pxl * b + pyl * d); 261 | this._target.transform("m" + a + "," + b + "," + c + "," + d + "," + (pt.tx + pt.ox) + "," + (pt.ty + pt.oy)); 262 | } 263 | 264 | } 265 | 266 | }), 267 | p = RaphaelPlugin.prototype; 268 | 269 | //compares the beginning x, y, scaleX, scaleY, rotation, and skewX properties with the ending ones and adds PropTweens accordingly wherever necessary. We must tween them individually (rather than just tweening the matrix values) so that elgant overwriting can occur, like if one tween is controlling scaleX, scaleY, and rotation and then another one starts mid-tween that is trying to control the scaleX only - this tween should continue tweening scaleY and rotation. 270 | p._parseTransform = function(t, v) { 271 | if (this._transform) { return; } //only need to parse the transform once, and only if the browser supports it. 272 | 273 | var m1 = this._transform = _getTransform(t, true), 274 | min = 0.000001, 275 | m2, skewY, p, pt, copy, dx, dy, mtx, pivot; 276 | 277 | if (typeof(v) === "object") { //for values like scaleX, scaleY, rotation, x, y, skewX, and skewY or transform:{...} (object) 278 | 279 | m2 = {scaleX:_parseVal((v.scaleX != null) ? v.scaleX : v.scale, m1.scaleX), 280 | scaleY:_parseVal((v.scaleY != null) ? v.scaleY : v.scale, m1.scaleY), 281 | tx:_parseVal(v.tx, m1.tx), 282 | ty:_parseVal(v.ty, m1.ty)}; 283 | 284 | if (v.shortRotation != null) { 285 | m2.rotation = (typeof(v.shortRotation) === "number") ? v.shortRotation * _DEG2RAD : _parseAngle(v.shortRotation, m1.rotation); 286 | var dif = (m2.rotation - m1.rotation) % (Math.PI * 2); 287 | if (dif !== dif % Math.PI) { 288 | dif += Math.PI * ((dif < 0) ? 2 : -2); 289 | } 290 | m2.rotation = m1.rotation + dif; 291 | 292 | } else { 293 | m2.rotation = (v.rotation == null) ? m1.rotation : (typeof(v.rotation) === "number") ? v.rotation * _DEG2RAD : _parseAngle(v.rotation, m1.rotation); 294 | } 295 | m2.skewX = (v.skewX == null) ? m1.skewX : (typeof(v.skewX) === "number") ? v.skewX * _DEG2RAD : _parseAngle(v.skewX, m1.skewX); 296 | 297 | //note: for performance reasons, we combine all skewing into the skewX and rotation values, ignoring skewY but we must still record it so that we can discern how much of the overall skew is attributed to skewX vs. skewY. Otherwise, if the skewY would always act relative (tween skewY to 10deg, for example, multiple times and if we always combine things into skewX, we can't remember that skewY was 10 from last time). Remember, a skewY of 10 degrees looks the same as a rotation of 10 degrees plus a skewX of -10 degrees. 298 | m2.skewY = (v.skewY == null) ? m1.skewY : (typeof(v.skewY) === "number") ? v.skewY * _DEG2RAD : _parseAngle(v.skewY, m1.skewY); 299 | if ((skewY = m2.skewY - m1.skewY)) { 300 | m2.skewX += skewY; 301 | m2.rotation += skewY; 302 | } 303 | //don't allow rotation/skew values to be a SUPER small decimal because when they're translated back to strings for setting the css property, the browser reports them in a funky way, like 1-e7. Of course we could use toFixed() to resolve that issue but that hurts performance quite a bit with all those function calls on every frame, plus it is virtually impossible to discern values that small visually (nobody will notice changing a rotation of 0.0000001 to 0, so the performance improvement is well worth it). 304 | if (m2.skewY < min) if (m2.skewY > -min) { 305 | m2.skewY = 0; 306 | } 307 | if (m2.skewX < min) if (m2.skewX > -min) { 308 | m2.skewX = 0; 309 | } 310 | if (m2.rotation < min) if (m2.rotation > -min) { 311 | m2.rotation = 0; 312 | } 313 | 314 | pivot = v.localPivot || v.globalPivot; 315 | 316 | if (typeof(pivot) === "string") { 317 | copy = pivot.split(","); 318 | dx = Number(copy[0]); 319 | dy = Number(copy[1]); 320 | } else if (typeof(pivot) === "object") { 321 | dx = Number(pivot.x); 322 | dy = Number(pivot.y); 323 | } else if (v.localPivot) { 324 | copy = t.getBBox(true); 325 | dx = copy.width / 2; 326 | dy = copy.height / 2; 327 | } else { 328 | copy = t.getBBox(); 329 | dx = copy.x + copy.width / 2; 330 | dy = copy.y + copy.height / 2; 331 | } 332 | 333 | if (v.localPivot) { 334 | mtx = t.matrix; 335 | dx += t.attr("x"); 336 | dy += t.attr("y"); 337 | this._pxl = dx; 338 | this._pyl = dy; 339 | this._pxg = dx * mtx.a + dy * mtx.c + mtx.e - m1.tx; 340 | this._pyg = dx * mtx.b + dy * mtx.d + mtx.f - m1.ty; 341 | } else { 342 | mtx = t.matrix.invert(); 343 | this._pxl = dx * mtx.a + dy * mtx.c + mtx.e; 344 | this._pyl = dx * mtx.b + dy * mtx.d + mtx.f; 345 | this._pxg = dx - m1.tx; 346 | this._pyg = dy - m1.ty; 347 | } 348 | 349 | } else if (typeof(v) === "string") { //for values like transform:"rotate(60deg) scale(0.5, 0.8)" 350 | copy = this._target.transform(); 351 | t.transform(v); 352 | m2 = _getTransform(t, false); 353 | t.transform(copy); 354 | } else { 355 | return; 356 | } 357 | 358 | for (p in _transformMap) { 359 | if (m1[p] !== m2[p]) if (p !== "shortRotation") if (p !== "scale") { 360 | this._firstPT = pt = {_next:this._firstPT, t:m1, p:p, s:m1[p], c:m2[p] - m1[p], n:p, f:false, r:false, b:m1[p], e:m2[p], type:0}; 361 | if (pt._next) { 362 | pt._next._prev = pt; 363 | } 364 | this._overwriteProps.push("raphael_" + p); 365 | } 366 | } 367 | }; 368 | 369 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/PixiPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 0.1.2 3 | * DATE: 2017-06-29 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * PixiPlugin is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | */ 12 | var _gsScope = (typeof module !== "undefined" && module.exports && typeof global !== "undefined") ? global : this || window; 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push(function () { 14 | "use strict"; 15 | 16 | var _numExp = /(\d|\.)+/g, 17 | _relNumExp = /(?:\d|\-\d|\.\d|\-\.\d|\+=\d|\-=\d|\+=.\d|\-=\.\d)+/g, 18 | _colorLookup = {aqua:[0,255,255], 19 | lime:[0,255,0], 20 | silver:[192,192,192], 21 | black:[0,0,0], 22 | maroon:[128,0,0], 23 | teal:[0,128,128], 24 | blue:[0,0,255], 25 | navy:[0,0,128], 26 | white:[255,255,255], 27 | fuchsia:[255,0,255], 28 | olive:[128,128,0], 29 | yellow:[255,255,0], 30 | orange:[255,165,0], 31 | gray:[128,128,128], 32 | purple:[128,0,128], 33 | green:[0,128,0], 34 | red:[255,0,0], 35 | pink:[255,192,203], 36 | cyan:[0,255,255], 37 | transparent:[255,255,255,0]}, 38 | _hue = function(h, m1, m2) { 39 | h = (h < 0) ? h + 1 : (h > 1) ? h - 1 : h; 40 | return ((((h * 6 < 1) ? m1 + (m2 - m1) * h * 6 : (h < 0.5) ? m2 : (h * 3 < 2) ? m1 + (m2 - m1) * (2 / 3 - h) * 6 : m1) * 255) + 0.5) | 0; 41 | }, 42 | /** 43 | * @private Parses a color (like #9F0, #FF9900, rgb(255,51,153) or hsl(108, 50%, 10%)) into an array with 3 elements for red, green, and blue or if "format" parameter is "hsl", it will populate the array with hue, saturation, and lightness values. Or if "format" is "number", it'll return a number like 0xFF0000 instead of an array. If a relative value is found in an hsl() or hsla() string, it will preserve those relative prefixes and all the values in the array will be strings instead of numbers (in all other cases it will be populated with numbers). 44 | * @param {(string|number)} v The value the should be parsed which could be a string like #9F0 or rgb(255,102,51) or rgba(255,0,0,0.5) or it could be a number like 0xFF00CC or even a named color like red, blue, purple, etc. 45 | * @param {(string)} format If "hsl", an hsl() or hsla() value will be returned instead of rgb() or rgba(). Or if "number", then a numeric value will be returned, like 0xFF0000. Default is rgb. 46 | * @return {(array|number)} An array containing red, green, and blue (and optionally alpha) in that order, or if the format parameter was "hsl", the array will contain hue, saturation and lightness (and optionally alpha) in that order. Or if "format" is defined as "number", it'll return a number like 0xFF0000. Always numbers unless there's a relative prefix found in an hsl() or hsla() string and "format" is "hsl". 47 | */ 48 | _parseColor = function(v, format) { 49 | var toHSL = (format === "hsl"), 50 | a, r, g, b, h, s, l, max, min, d, wasHSL; 51 | if (!v) { 52 | a = _colorLookup.black; 53 | } else if (typeof(v) === "number") { 54 | a = [v >> 16, (v >> 8) & 255, v & 255]; 55 | } else { 56 | if (v.charAt(v.length - 1) === ",") { //sometimes a trailing comma is included and we should chop it off (typically from a comma-delimited list of values like a textShadow:"2px 2px 2px blue, 5px 5px 5px rgb(255,0,0)" - in this example "blue," has a trailing comma. We could strip it out inside parseComplex() but we'd need to do it to the beginning and ending values plus it wouldn't provide protection from other potential scenarios like if the user passes in a similar value. 57 | v = v.substr(0, v.length - 1); 58 | } 59 | if (_colorLookup[v]) { 60 | a = _colorLookup[v]; 61 | } else if (v.charAt(0) === "#") { 62 | if (v.length === 4) { //for shorthand like #9F0 63 | r = v.charAt(1); 64 | g = v.charAt(2); 65 | b = v.charAt(3); 66 | v = "#" + r + r + g + g + b + b; 67 | } 68 | v = parseInt(v.substr(1), 16); 69 | a = [v >> 16, (v >> 8) & 255, v & 255]; 70 | } else if (v.substr(0, 3) === "hsl") { 71 | a = wasHSL = v.match(_numExp); 72 | if (!toHSL) { 73 | h = (Number(a[0]) % 360) / 360; 74 | s = Number(a[1]) / 100; 75 | l = Number(a[2]) / 100; 76 | g = (l <= 0.5) ? l * (s + 1) : l + s - l * s; 77 | r = l * 2 - g; 78 | if (a.length > 3) { 79 | a[3] = Number(v[3]); 80 | } 81 | a[0] = _hue(h + 1 / 3, r, g); 82 | a[1] = _hue(h, r, g); 83 | a[2] = _hue(h - 1 / 3, r, g); 84 | } else if (v.indexOf("=") !== -1) { //if relative values are found, just return the raw strings with the relative prefixes in place. 85 | return v.match(_relNumExp); 86 | } 87 | } else { 88 | a = v.match(_numExp) || _colorLookup.transparent; 89 | } 90 | a[0] = Number(a[0]); 91 | a[1] = Number(a[1]); 92 | a[2] = Number(a[2]); 93 | if (a.length > 3) { 94 | a[3] = Number(a[3]); 95 | } 96 | } 97 | if (toHSL && !wasHSL) { 98 | r = a[0] / 255; 99 | g = a[1] / 255; 100 | b = a[2] / 255; 101 | max = Math.max(r, g, b); 102 | min = Math.min(r, g, b); 103 | l = (max + min) / 2; 104 | if (max === min) { 105 | h = s = 0; 106 | } else { 107 | d = max - min; 108 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 109 | h = (max === r) ? (g - b) / d + (g < b ? 6 : 0) : (max === g) ? (b - r) / d + 2 : (r - g) / d + 4; 110 | h *= 60; 111 | } 112 | a[0] = (h + 0.5) | 0; 113 | a[1] = (s * 100 + 0.5) | 0; 114 | a[2] = (l * 100 + 0.5) | 0; 115 | } 116 | return (format === "number") ? (a[0] << 16 | a[1] << 8 | a[2]) : a; 117 | }, 118 | _formatColors = function(s, toHSL) { 119 | var colors = (s + "").match(_colorExp) || [], 120 | charIndex = 0, 121 | parsed = "", 122 | i, color, temp; 123 | if (!colors.length) { 124 | return s; 125 | } 126 | for (i = 0; i < colors.length; i++) { 127 | color = colors[i]; 128 | temp = s.substr(charIndex, s.indexOf(color, charIndex)-charIndex); 129 | charIndex += temp.length + color.length; 130 | color = _parseColor(color, (toHSL ? "hsl" : "rgb")); 131 | if (color.length === 3) { 132 | color.push(1); 133 | } 134 | parsed += temp + (toHSL ? "hsla(" + color[0] + "," + color[1] + "%," + color[2] + "%," + color[3] : "rgba(" + color.join(",")) + ")"; 135 | } 136 | return parsed + s.substr(charIndex); 137 | }, _colorStringFilter, 138 | TweenLite = (_gsScope.GreenSockGlobals || _gsScope).TweenLite, 139 | _colorExp = "(?:\\b(?:(?:rgb|rgba|hsl|hsla)\\(.+?\\))|\\B#(?:[0-9a-f]{3}){1,2}\\b", //we'll dynamically build this Regular Expression to conserve file size. After building it, it will be able to find rgb(), rgba(), # (hexadecimal), and named color values like red, blue, purple, etc. 140 | 141 | _idMatrix = [1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0], 142 | _lumR = 0.212671, 143 | _lumG = 0.715160, 144 | _lumB = 0.072169, 145 | 146 | _applyMatrix = function(m, m2) { 147 | var temp = [], 148 | i = 0, 149 | z = 0, 150 | y, x; 151 | for (y = 0; y < 4; y++) { 152 | for (x = 0; x < 5; x++) { 153 | z = (x === 4) ? m[i + 4] : 0; 154 | temp[i + x] = m[i] * m2[x] + m[i+1] * m2[x + 5] + m[i+2] * m2[x + 10] + m[i+3] * m2[x + 15] + z; 155 | } 156 | i += 5; 157 | } 158 | return temp; 159 | }, 160 | 161 | _setSaturation = function(m, n) { 162 | var inv = 1 - n, 163 | r = inv * _lumR, 164 | g = inv * _lumG, 165 | b = inv * _lumB; 166 | return _applyMatrix([r + n, g, b, 0, 0, r, g + n, b, 0, 0, r, g, b + n, 0, 0, 0, 0, 0, 1, 0], m); 167 | }, 168 | 169 | _colorize = function(m, color, amount) { 170 | var c = _parseColor(color), 171 | r = c[0] / 255, 172 | g = c[1] / 255, 173 | b = c[2] / 255, 174 | inv = 1 - amount; 175 | return _applyMatrix([inv + amount * r * _lumR, amount * r * _lumG, amount * r * _lumB, 0, 0, amount * g * _lumR, inv + amount * g * _lumG, amount * g * _lumB, 0, 0, amount * b * _lumR, amount * b * _lumG, inv + amount * b * _lumB, 0, 0, 0, 0, 0, 1, 0], m); 176 | }, 177 | 178 | _setHue = function(m, n) { 179 | n *= Math.PI / 180; 180 | var c = Math.cos(n), 181 | s = Math.sin(n); 182 | return _applyMatrix([(_lumR + (c * (1 - _lumR))) + (s * (-_lumR)), (_lumG + (c * (-_lumG))) + (s * (-_lumG)), (_lumB + (c * (-_lumB))) + (s * (1 - _lumB)), 0, 0, (_lumR + (c * (-_lumR))) + (s * 0.143), (_lumG + (c * (1 - _lumG))) + (s * 0.14), (_lumB + (c * (-_lumB))) + (s * -0.283), 0, 0, (_lumR + (c * (-_lumR))) + (s * (-(1 - _lumR))), (_lumG + (c * (-_lumG))) + (s * _lumG), (_lumB + (c * (1 - _lumB))) + (s * _lumB), 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1], m); 183 | }, 184 | 185 | _setContrast = function(m, n) { 186 | return _applyMatrix([n,0,0,0,0.5 * (1 - n), 0,n,0,0,0.5 * (1 - n), 0,0,n,0,0.5 * (1 - n), 0,0,0,1,0], m); 187 | }, 188 | 189 | _getFilter = function(t, type) { 190 | var filterClass = _gsScope.PIXI.filters[type], 191 | filters = t.filters || [], 192 | i = filters.length, 193 | filter; 194 | if (!filterClass) { 195 | throw("PixiPlugin error: " + type + " isn't present."); 196 | } 197 | while (--i > -1) { 198 | if (filters[i] instanceof filterClass) { 199 | return filters[i]; 200 | } 201 | } 202 | filter = new filterClass(); 203 | if (type === "BlurFilter") { 204 | filter.blur = 0; 205 | } 206 | filters.push(filter); 207 | t.filters = filters; 208 | return filter; 209 | }, 210 | 211 | _addColorMatrixFilterCacheTween = function(p, pg, cache, vars) { //we cache the ColorMatrixFilter components in a _gsColorMatrixFilter object attached to the target object so that it's easy to grab the current value at any time. 212 | pg._addTween(cache, p, cache[p], vars[p], p); 213 | pg._overwriteProps.push(p); 214 | }, 215 | 216 | _applyBrightnessToMatrix = function(brightness, matrix) { 217 | var temp = new _gsScope.PIXI.filters.ColorMatrixFilter(); 218 | temp.matrix = matrix; 219 | temp.brightness(brightness, true); 220 | return temp.matrix; 221 | }, 222 | 223 | _CMFdefaults = {contrast:1, saturation:1, colorizeAmount:0, colorize:"rgb(255,255,255)", hue:0, brightness:1}, 224 | 225 | _parseColorMatrixFilter = function(t, v, pg) { 226 | var filter = _getFilter(t, "ColorMatrixFilter"), 227 | cache = t._gsColorMatrixFilter = t._gsColorMatrixFilter || {contrast:1, saturation:1, colorizeAmount:0, colorize:"rgb(255,255,255)", hue:0, brightness:1}, 228 | combine = v.combineCMF && !("colorMatrixFilter" in v && !v.colorMatrixFilter), 229 | i, matrix, startMatrix; 230 | startMatrix = filter.matrix; 231 | if (v.matrix && v.matrix.length === startMatrix.length) { 232 | matrix = v.matrix; 233 | if (cache.contrast !== 1) { 234 | _addColorMatrixFilterCacheTween("contrast", pg, cache, _CMFdefaults); 235 | } 236 | if (cache.hue) { 237 | _addColorMatrixFilterCacheTween("hue", pg, cache, _CMFdefaults); 238 | } 239 | if (cache.brightness !== 1) { 240 | _addColorMatrixFilterCacheTween("brightness", pg, cache, _CMFdefaults); 241 | } 242 | if (cache.colorizeAmount) { 243 | _addColorMatrixFilterCacheTween("colorize", pg, cache, _CMFdefaults); 244 | _addColorMatrixFilterCacheTween("colorizeAmount", pg, cache, _CMFdefaults); 245 | } 246 | if (cache.saturation !== 1) { 247 | _addColorMatrixFilterCacheTween("saturation", pg, cache, _CMFdefaults); 248 | } 249 | 250 | } else { 251 | matrix = _idMatrix.slice(); 252 | if (v.contrast != null) { 253 | matrix = _setContrast(matrix, Number(v.contrast)); 254 | _addColorMatrixFilterCacheTween("contrast", pg, cache, v); 255 | } else if (cache.contrast !== 1) { 256 | if (combine) { 257 | matrix = _setContrast(matrix, cache.contrast); 258 | } else { 259 | _addColorMatrixFilterCacheTween("contrast", pg, cache, _CMFdefaults); 260 | } 261 | } 262 | if (v.hue != null) { 263 | matrix = _setHue(matrix, Number(v.hue)); 264 | _addColorMatrixFilterCacheTween("hue", pg, cache, v); 265 | } else if (cache.hue) { 266 | if (combine) { 267 | matrix = _setHue(matrix, cache.hue); 268 | } else { 269 | _addColorMatrixFilterCacheTween("hue", pg, cache, _CMFdefaults); 270 | } 271 | } 272 | if (v.brightness != null) { 273 | matrix = _applyBrightnessToMatrix(Number(v.brightness), matrix); 274 | _addColorMatrixFilterCacheTween("brightness", pg, cache, v); 275 | } else if (cache.brightness !== 1) { 276 | if (combine) { 277 | matrix = _applyBrightnessToMatrix(cache.brightness, matrix); 278 | } else { 279 | _addColorMatrixFilterCacheTween("brightness", pg, cache, _CMFdefaults); 280 | } 281 | } 282 | if (v.colorize != null) { 283 | v.colorizeAmount = ("colorizeAmount" in v) ? Number(v.colorizeAmount) : 1; 284 | matrix = _colorize(matrix, v.colorize, v.colorizeAmount); 285 | _addColorMatrixFilterCacheTween("colorize", pg, cache, v); 286 | _addColorMatrixFilterCacheTween("colorizeAmount", pg, cache, v); 287 | } else if (cache.colorizeAmount) { 288 | if (combine) { 289 | matrix = _colorize(matrix, cache.colorize, cache.colorizeAmount); 290 | } else { 291 | _addColorMatrixFilterCacheTween("colorize", pg, cache, _CMFdefaults); 292 | _addColorMatrixFilterCacheTween("colorizeAmount", pg, cache, _CMFdefaults); 293 | } 294 | } 295 | if (v.saturation != null) { 296 | matrix = _setSaturation(matrix, Number(v.saturation)); 297 | _addColorMatrixFilterCacheTween("saturation", pg, cache, v); 298 | } else if (cache.saturation !== 1) { 299 | if (combine) { 300 | matrix = _setSaturation(matrix, cache.saturation); 301 | } else { 302 | _addColorMatrixFilterCacheTween("saturation", pg, cache, _CMFdefaults); 303 | } 304 | } 305 | } 306 | i = matrix.length; 307 | while (--i > -1) { 308 | if (matrix[i] !== startMatrix[i]) { 309 | pg._addTween(startMatrix, i, startMatrix[i], matrix[i], "colorMatrixFilter"); 310 | } 311 | } 312 | pg._overwriteProps.push("colorMatrixFilter"); 313 | }, 314 | 315 | _addColorTween = function(target, p, value, colorSetter, plugin) { 316 | var pt = colorSetter._firstPT = {_next:colorSetter._firstPT, t:target, p:p, proxy:{}, f:(typeof(target[p]) === "function")}; 317 | pt.proxy[p] = "rgb(" + _parseColor(!pt.f ? target[p] : target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ]()).join(",") + ")"; 318 | plugin._addTween(pt.proxy, p, "get", ((typeof(value) === "number") ? "rgb(" + _parseColor(value, false).join(",") + ")" : value), p, null, null, _colorStringFilter); 319 | }, 320 | 321 | //to improve performance, when a color is sensed, we hijack the setRatio() method of the plugin instance with a new function that this method spits back. This is a special method that handles parsing color values on-the-fly and turns them into numeric values which PixiJS requires. In other words, instead of "rgb(255, 0, 0)", PixiJS wants 0xFF0000. This also works with hsl() values. 322 | _buildColorSetter = function(tween, plugin) { 323 | var setRatio = plugin.setRatio, //save the original (super) setRatio() function 324 | func = function(v) { 325 | var pt = func._firstPT, 326 | val; 327 | setRatio.call(plugin, v); 328 | while (pt) { 329 | val = _parseColor(pt.proxy[pt.p], "number"); 330 | if (pt.f) { 331 | pt.t[pt.p](val); 332 | } else { 333 | pt.t[pt.p] = val; 334 | } 335 | pt = pt._next; 336 | } 337 | if (func.graphics) { //in order for PixiJS to actually redraw GraphicsData, we've gotta increment the "dirty" and "clearDirty" values. If we don't do this, the values will be tween properly, but not rendered. 338 | func.graphics.dirty++; 339 | func.graphics.clearDirty++; 340 | } 341 | }; 342 | plugin.setRatio = func; 343 | return func; 344 | }, 345 | 346 | 347 | _colorProps = {tint:1, lineColor:1, fillColor:1}, 348 | _xyContexts = "position,scale,skew,pivot,anchor,tilePosition,tileScale".split(","), 349 | _contexts = {x:"position", y:"position", tileX:"tilePosition", tileY:"tilePosition"}, 350 | _colorMatrixFilterProps = {colorMatrixFilter:1, saturation:1, contrast:1, hue:1, colorize:1, colorizeAmount:1, brightness:1, combineCMF:1}, 351 | _DEG2RAD = Math.PI / 180, 352 | _degreesToRadians = function(value) { 353 | return (typeof(value) === "string" && value.charAt(1) === "=") ? value.substr(0, 2) + (parseFloat(value.substr(2)) * _DEG2RAD) : value * _DEG2RAD; 354 | }, i, p; 355 | 356 | //context setup... 357 | for (i = 0; i < _xyContexts.length; i++) { 358 | p = _xyContexts[i]; 359 | _contexts[p + "X"] = p; 360 | _contexts[p + "Y"] = p; 361 | } 362 | 363 | //color parsing setup... 364 | for (p in _colorLookup) { 365 | _colorExp += "|" + p + "\\b"; 366 | } 367 | _colorExp = new RegExp(_colorExp+")", "gi"); 368 | _colorStringFilter = function(a) { 369 | var combined = a[0] + " " + a[1], 370 | toHSL; 371 | _colorExp.lastIndex = 0; 372 | if (_colorExp.test(combined)) { 373 | toHSL = (combined.indexOf("hsl(") !== -1 || combined.indexOf("hsla(") !== -1); 374 | a[0] = _formatColors(a[0], toHSL); 375 | a[1] = _formatColors(a[1], toHSL); 376 | } 377 | }; 378 | 379 | if (!TweenLite.defaultStringFilter) { 380 | TweenLite.defaultStringFilter = _colorStringFilter; 381 | } 382 | 383 | var PixiPlugin = _gsScope._gsDefine.plugin({ 384 | propName: "pixi", 385 | priority: 0, 386 | API: 2, 387 | global: true, 388 | version: "0.1.2", 389 | 390 | init: function (target, values, tween, index) { 391 | if (!target instanceof _gsScope.PIXI.DisplayObject) { 392 | return false; 393 | } 394 | var context, axis, value, colorMatrix, filter, p, padding, colorSetter, i, data; 395 | for (p in values) { 396 | context = _contexts[p]; 397 | value = values[p]; 398 | if (typeof(value) === "function") { 399 | value = value(index || 0, target); 400 | } 401 | if (context) { 402 | axis = (p.charAt(p.length-1).toLowerCase().indexOf("x") !== -1) ? "x" : "y"; 403 | this._addTween(target[context], axis, target[context][axis], (context === "skew") ? _degreesToRadians(value) : value, p); 404 | } else if (p === "scale" || p === "anchor" || p === "pivot" || p === "tileScale") { 405 | this._addTween(target[p], "x", target[p].x, value, p + "X"); 406 | this._addTween(target[p], "y", target[p].y, value, p + "Y"); 407 | } else if (p === "rotation") { //PIXI expects rotation in radians, but as a convenience we let folks define it in degrees and we do the conversion. 408 | this._addTween(target, p, target.rotation, _degreesToRadians(value), p); 409 | 410 | } else if (_colorMatrixFilterProps[p]) { 411 | if (!colorMatrix) { 412 | _parseColorMatrixFilter(target, values.colorMatrixFilter || values, this); 413 | colorMatrix = true; 414 | } 415 | } else if (p === "blur" || p === "blurX" || p === "blurY" || p === "blurPadding") { 416 | filter = _getFilter(target, "BlurFilter"); 417 | this._addTween(filter, p, filter[p], value, p); 418 | if (values.blurPadding !== 0) { 419 | padding = values.blurPadding || Math.max(filter[p], value) * 2; 420 | i = target.filters.length; 421 | while (--i > -1) { 422 | target.filters[i].padding = Math.max(target.filters[i].padding, padding); //if we don't expand the padding on all the filters, it can look clipped. 423 | } 424 | } 425 | } else if (_colorProps[p]) { 426 | if (!colorSetter) { 427 | colorSetter = _buildColorSetter(tween, this); 428 | } 429 | if ((p === "lineColor" || p === "fillColor") && target instanceof _gsScope.PIXI.Graphics) { 430 | data = target.graphicsData; 431 | i = data.length; 432 | while (--i > -1) { 433 | _addColorTween(data[i], p, value, colorSetter, this); 434 | } 435 | colorSetter.graphics = target; 436 | } else { 437 | _addColorTween(target, p, value, colorSetter, this); 438 | } 439 | } else { 440 | this._addTween(target, p, target[p], value, p); 441 | } 442 | this._overwriteProps.push(p); 443 | } 444 | return true; 445 | } 446 | }); 447 | 448 | PixiPlugin.colorProps = _colorProps; 449 | PixiPlugin.parseColor = _parseColor; 450 | PixiPlugin.formatColors = _formatColors; 451 | PixiPlugin.colorStringFilter = _colorStringFilter; 452 | 453 | 454 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 455 | 456 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 457 | (function(name) { 458 | "use strict"; 459 | var getGlobal = function() { 460 | return (_gsScope.GreenSockGlobals || _gsScope)[name]; 461 | }; 462 | if (typeof(module) !== "undefined" && module.exports) { //node 463 | require("../TweenLite.js"); 464 | module.exports = getGlobal(); 465 | } else if (typeof(define) === "function" && define.amd) { //AMD 466 | define(["TweenLite"], getGlobal); 467 | } 468 | }("PixiPlugin")); -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/BezierPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 1.3.8 3 | * DATE: 2017-06-19 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | **/ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | var _RAD2DEG = 180 / Math.PI, 18 | _r1 = [], 19 | _r2 = [], 20 | _r3 = [], 21 | _corProps = {}, 22 | _globals = _gsScope._gsDefine.globals, 23 | Segment = function(a, b, c, d) { 24 | if (c === d) { //if c and d match, the final autoRotate value could lock at -90 degrees, so differentiate them slightly. 25 | c = d - (d - b) / 1000000; 26 | } 27 | if (a === b) { //if a and b match, the starting autoRotate value could lock at -90 degrees, so differentiate them slightly. 28 | b = a + (c - a) / 1000000; 29 | } 30 | this.a = a; 31 | this.b = b; 32 | this.c = c; 33 | this.d = d; 34 | this.da = d - a; 35 | this.ca = c - a; 36 | this.ba = b - a; 37 | }, 38 | _correlate = ",x,y,z,left,top,right,bottom,marginTop,marginLeft,marginRight,marginBottom,paddingLeft,paddingTop,paddingRight,paddingBottom,backgroundPosition,backgroundPosition_y,", 39 | cubicToQuadratic = function(a, b, c, d) { 40 | var q1 = {a:a}, 41 | q2 = {}, 42 | q3 = {}, 43 | q4 = {c:d}, 44 | mab = (a + b) / 2, 45 | mbc = (b + c) / 2, 46 | mcd = (c + d) / 2, 47 | mabc = (mab + mbc) / 2, 48 | mbcd = (mbc + mcd) / 2, 49 | m8 = (mbcd - mabc) / 8; 50 | q1.b = mab + (a - mab) / 4; 51 | q2.b = mabc + m8; 52 | q1.c = q2.a = (q1.b + q2.b) / 2; 53 | q2.c = q3.a = (mabc + mbcd) / 2; 54 | q3.b = mbcd - m8; 55 | q4.b = mcd + (d - mcd) / 4; 56 | q3.c = q4.a = (q3.b + q4.b) / 2; 57 | return [q1, q2, q3, q4]; 58 | }, 59 | _calculateControlPoints = function(a, curviness, quad, basic, correlate) { 60 | var l = a.length - 1, 61 | ii = 0, 62 | cp1 = a[0].a, 63 | i, p1, p2, p3, seg, m1, m2, mm, cp2, qb, r1, r2, tl; 64 | for (i = 0; i < l; i++) { 65 | seg = a[ii]; 66 | p1 = seg.a; 67 | p2 = seg.d; 68 | p3 = a[ii+1].d; 69 | 70 | if (correlate) { 71 | r1 = _r1[i]; 72 | r2 = _r2[i]; 73 | tl = ((r2 + r1) * curviness * 0.25) / (basic ? 0.5 : _r3[i] || 0.5); 74 | m1 = p2 - (p2 - p1) * (basic ? curviness * 0.5 : (r1 !== 0 ? tl / r1 : 0)); 75 | m2 = p2 + (p3 - p2) * (basic ? curviness * 0.5 : (r2 !== 0 ? tl / r2 : 0)); 76 | mm = p2 - (m1 + (((m2 - m1) * ((r1 * 3 / (r1 + r2)) + 0.5) / 4) || 0)); 77 | } else { 78 | m1 = p2 - (p2 - p1) * curviness * 0.5; 79 | m2 = p2 + (p3 - p2) * curviness * 0.5; 80 | mm = p2 - (m1 + m2) / 2; 81 | } 82 | m1 += mm; 83 | m2 += mm; 84 | 85 | seg.c = cp2 = m1; 86 | if (i !== 0) { 87 | seg.b = cp1; 88 | } else { 89 | seg.b = cp1 = seg.a + (seg.c - seg.a) * 0.6; //instead of placing b on a exactly, we move it inline with c so that if the user specifies an ease like Back.easeIn or Elastic.easeIn which goes BEYOND the beginning, it will do so smoothly. 90 | } 91 | 92 | seg.da = p2 - p1; 93 | seg.ca = cp2 - p1; 94 | seg.ba = cp1 - p1; 95 | 96 | if (quad) { 97 | qb = cubicToQuadratic(p1, cp1, cp2, p2); 98 | a.splice(ii, 1, qb[0], qb[1], qb[2], qb[3]); 99 | ii += 4; 100 | } else { 101 | ii++; 102 | } 103 | 104 | cp1 = m2; 105 | } 106 | seg = a[ii]; 107 | seg.b = cp1; 108 | seg.c = cp1 + (seg.d - cp1) * 0.4; //instead of placing c on d exactly, we move it inline with b so that if the user specifies an ease like Back.easeOut or Elastic.easeOut which goes BEYOND the end, it will do so smoothly. 109 | seg.da = seg.d - seg.a; 110 | seg.ca = seg.c - seg.a; 111 | seg.ba = cp1 - seg.a; 112 | if (quad) { 113 | qb = cubicToQuadratic(seg.a, cp1, seg.c, seg.d); 114 | a.splice(ii, 1, qb[0], qb[1], qb[2], qb[3]); 115 | } 116 | }, 117 | _parseAnchors = function(values, p, correlate, prepend) { 118 | var a = [], 119 | l, i, p1, p2, p3, tmp; 120 | if (prepend) { 121 | values = [prepend].concat(values); 122 | i = values.length; 123 | while (--i > -1) { 124 | if (typeof( (tmp = values[i][p]) ) === "string") if (tmp.charAt(1) === "=") { 125 | values[i][p] = prepend[p] + Number(tmp.charAt(0) + tmp.substr(2)); //accommodate relative values. Do it inline instead of breaking it out into a function for speed reasons 126 | } 127 | } 128 | } 129 | l = values.length - 2; 130 | if (l < 0) { 131 | a[0] = new Segment(values[0][p], 0, 0, values[0][p]); 132 | return a; 133 | } 134 | for (i = 0; i < l; i++) { 135 | p1 = values[i][p]; 136 | p2 = values[i+1][p]; 137 | a[i] = new Segment(p1, 0, 0, p2); 138 | if (correlate) { 139 | p3 = values[i+2][p]; 140 | _r1[i] = (_r1[i] || 0) + (p2 - p1) * (p2 - p1); 141 | _r2[i] = (_r2[i] || 0) + (p3 - p2) * (p3 - p2); 142 | } 143 | } 144 | a[i] = new Segment(values[i][p], 0, 0, values[i+1][p]); 145 | return a; 146 | }, 147 | bezierThrough = function(values, curviness, quadratic, basic, correlate, prepend) { 148 | var obj = {}, 149 | props = [], 150 | first = prepend || values[0], 151 | i, p, a, j, r, l, seamless, last; 152 | correlate = (typeof(correlate) === "string") ? ","+correlate+"," : _correlate; 153 | if (curviness == null) { 154 | curviness = 1; 155 | } 156 | for (p in values[0]) { 157 | props.push(p); 158 | } 159 | //check to see if the last and first values are identical (well, within 0.05). If so, make seamless by appending the second element to the very end of the values array and the 2nd-to-last element to the very beginning (we'll remove those segments later) 160 | if (values.length > 1) { 161 | last = values[values.length - 1]; 162 | seamless = true; 163 | i = props.length; 164 | while (--i > -1) { 165 | p = props[i]; 166 | if (Math.abs(first[p] - last[p]) > 0.05) { //build in a tolerance of +/-0.05 to accommodate rounding errors. 167 | seamless = false; 168 | break; 169 | } 170 | } 171 | if (seamless) { 172 | values = values.concat(); //duplicate the array to avoid contaminating the original which the user may be reusing for other tweens 173 | if (prepend) { 174 | values.unshift(prepend); 175 | } 176 | values.push(values[1]); 177 | prepend = values[values.length - 3]; 178 | } 179 | } 180 | _r1.length = _r2.length = _r3.length = 0; 181 | i = props.length; 182 | while (--i > -1) { 183 | p = props[i]; 184 | _corProps[p] = (correlate.indexOf(","+p+",") !== -1); 185 | obj[p] = _parseAnchors(values, p, _corProps[p], prepend); 186 | } 187 | i = _r1.length; 188 | while (--i > -1) { 189 | _r1[i] = Math.sqrt(_r1[i]); 190 | _r2[i] = Math.sqrt(_r2[i]); 191 | } 192 | if (!basic) { 193 | i = props.length; 194 | while (--i > -1) { 195 | if (_corProps[p]) { 196 | a = obj[props[i]]; 197 | l = a.length - 1; 198 | for (j = 0; j < l; j++) { 199 | r = (a[j+1].da / _r2[j] + a[j].da / _r1[j]) || 0; 200 | _r3[j] = (_r3[j] || 0) + r * r; 201 | } 202 | } 203 | } 204 | i = _r3.length; 205 | while (--i > -1) { 206 | _r3[i] = Math.sqrt(_r3[i]); 207 | } 208 | } 209 | i = props.length; 210 | j = quadratic ? 4 : 1; 211 | while (--i > -1) { 212 | p = props[i]; 213 | a = obj[p]; 214 | _calculateControlPoints(a, curviness, quadratic, basic, _corProps[p]); //this method requires that _parseAnchors() and _setSegmentRatios() ran first so that _r1, _r2, and _r3 values are populated for all properties 215 | if (seamless) { 216 | a.splice(0, j); 217 | a.splice(a.length - j, j); 218 | } 219 | } 220 | return obj; 221 | }, 222 | _parseBezierData = function(values, type, prepend) { 223 | type = type || "soft"; 224 | var obj = {}, 225 | inc = (type === "cubic") ? 3 : 2, 226 | soft = (type === "soft"), 227 | props = [], 228 | a, b, c, d, cur, i, j, l, p, cnt, tmp; 229 | if (soft && prepend) { 230 | values = [prepend].concat(values); 231 | } 232 | if (values == null || values.length < inc + 1) { throw "invalid Bezier data"; } 233 | for (p in values[0]) { 234 | props.push(p); 235 | } 236 | i = props.length; 237 | while (--i > -1) { 238 | p = props[i]; 239 | obj[p] = cur = []; 240 | cnt = 0; 241 | l = values.length; 242 | for (j = 0; j < l; j++) { 243 | a = (prepend == null) ? values[j][p] : (typeof( (tmp = values[j][p]) ) === "string" && tmp.charAt(1) === "=") ? prepend[p] + Number(tmp.charAt(0) + tmp.substr(2)) : Number(tmp); 244 | if (soft) if (j > 1) if (j < l - 1) { 245 | cur[cnt++] = (a + cur[cnt-2]) / 2; 246 | } 247 | cur[cnt++] = a; 248 | } 249 | l = cnt - inc + 1; 250 | cnt = 0; 251 | for (j = 0; j < l; j += inc) { 252 | a = cur[j]; 253 | b = cur[j+1]; 254 | c = cur[j+2]; 255 | d = (inc === 2) ? 0 : cur[j+3]; 256 | cur[cnt++] = tmp = (inc === 3) ? new Segment(a, b, c, d) : new Segment(a, (2 * b + a) / 3, (2 * b + c) / 3, c); 257 | } 258 | cur.length = cnt; 259 | } 260 | return obj; 261 | }, 262 | _addCubicLengths = function(a, steps, resolution) { 263 | var inc = 1 / resolution, 264 | j = a.length, 265 | d, d1, s, da, ca, ba, p, i, inv, bez, index; 266 | while (--j > -1) { 267 | bez = a[j]; 268 | s = bez.a; 269 | da = bez.d - s; 270 | ca = bez.c - s; 271 | ba = bez.b - s; 272 | d = d1 = 0; 273 | for (i = 1; i <= resolution; i++) { 274 | p = inc * i; 275 | inv = 1 - p; 276 | d = d1 - (d1 = (p * p * da + 3 * inv * (p * ca + inv * ba)) * p); 277 | index = j * resolution + i - 1; 278 | steps[index] = (steps[index] || 0) + d * d; 279 | } 280 | } 281 | }, 282 | _parseLengthData = function(obj, resolution) { 283 | resolution = resolution >> 0 || 6; 284 | var a = [], 285 | lengths = [], 286 | d = 0, 287 | total = 0, 288 | threshold = resolution - 1, 289 | segments = [], 290 | curLS = [], //current length segments array 291 | p, i, l, index; 292 | for (p in obj) { 293 | _addCubicLengths(obj[p], a, resolution); 294 | } 295 | l = a.length; 296 | for (i = 0; i < l; i++) { 297 | d += Math.sqrt(a[i]); 298 | index = i % resolution; 299 | curLS[index] = d; 300 | if (index === threshold) { 301 | total += d; 302 | index = (i / resolution) >> 0; 303 | segments[index] = curLS; 304 | lengths[index] = total; 305 | d = 0; 306 | curLS = []; 307 | } 308 | } 309 | return {length:total, lengths:lengths, segments:segments}; 310 | }, 311 | 312 | 313 | 314 | BezierPlugin = _gsScope._gsDefine.plugin({ 315 | propName: "bezier", 316 | priority: -1, 317 | version: "1.3.8", 318 | API: 2, 319 | global:true, 320 | 321 | //gets called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 322 | init: function(target, vars, tween) { 323 | this._target = target; 324 | if (vars instanceof Array) { 325 | vars = {values:vars}; 326 | } 327 | this._func = {}; 328 | this._mod = {}; 329 | this._props = []; 330 | this._timeRes = (vars.timeResolution == null) ? 6 : parseInt(vars.timeResolution, 10); 331 | var values = vars.values || [], 332 | first = {}, 333 | second = values[0], 334 | autoRotate = vars.autoRotate || tween.vars.orientToBezier, 335 | p, isFunc, i, j, prepend; 336 | 337 | this._autoRotate = autoRotate ? (autoRotate instanceof Array) ? autoRotate : [["x","y","rotation",((autoRotate === true) ? 0 : Number(autoRotate) || 0)]] : null; 338 | for (p in second) { 339 | this._props.push(p); 340 | } 341 | 342 | i = this._props.length; 343 | while (--i > -1) { 344 | p = this._props[i]; 345 | 346 | this._overwriteProps.push(p); 347 | isFunc = this._func[p] = (typeof(target[p]) === "function"); 348 | first[p] = (!isFunc) ? parseFloat(target[p]) : target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ](); 349 | if (!prepend) if (first[p] !== values[0][p]) { 350 | prepend = first; 351 | } 352 | } 353 | this._beziers = (vars.type !== "cubic" && vars.type !== "quadratic" && vars.type !== "soft") ? bezierThrough(values, isNaN(vars.curviness) ? 1 : vars.curviness, false, (vars.type === "thruBasic"), vars.correlate, prepend) : _parseBezierData(values, vars.type, first); 354 | this._segCount = this._beziers[p].length; 355 | 356 | if (this._timeRes) { 357 | var ld = _parseLengthData(this._beziers, this._timeRes); 358 | this._length = ld.length; 359 | this._lengths = ld.lengths; 360 | this._segments = ld.segments; 361 | this._l1 = this._li = this._s1 = this._si = 0; 362 | this._l2 = this._lengths[0]; 363 | this._curSeg = this._segments[0]; 364 | this._s2 = this._curSeg[0]; 365 | this._prec = 1 / this._curSeg.length; 366 | } 367 | 368 | if ((autoRotate = this._autoRotate)) { 369 | this._initialRotations = []; 370 | if (!(autoRotate[0] instanceof Array)) { 371 | this._autoRotate = autoRotate = [autoRotate]; 372 | } 373 | i = autoRotate.length; 374 | while (--i > -1) { 375 | for (j = 0; j < 3; j++) { 376 | p = autoRotate[i][j]; 377 | this._func[p] = (typeof(target[p]) === "function") ? target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ] : false; 378 | } 379 | p = autoRotate[i][2]; 380 | this._initialRotations[i] = (this._func[p] ? this._func[p].call(this._target) : this._target[p]) || 0; 381 | this._overwriteProps.push(p); 382 | } 383 | } 384 | this._startRatio = tween.vars.runBackwards ? 1 : 0; //we determine the starting ratio when the tween inits which is always 0 unless the tween has runBackwards:true (indicating it's a from() tween) in which case it's 1. 385 | return true; 386 | }, 387 | 388 | //called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.) 389 | set: function(v) { 390 | var segments = this._segCount, 391 | func = this._func, 392 | target = this._target, 393 | notStart = (v !== this._startRatio), 394 | curIndex, inv, i, p, b, t, val, l, lengths, curSeg; 395 | if (!this._timeRes) { 396 | curIndex = (v < 0) ? 0 : (v >= 1) ? segments - 1 : (segments * v) >> 0; 397 | t = (v - (curIndex * (1 / segments))) * segments; 398 | } else { 399 | lengths = this._lengths; 400 | curSeg = this._curSeg; 401 | v *= this._length; 402 | i = this._li; 403 | //find the appropriate segment (if the currently cached one isn't correct) 404 | if (v > this._l2 && i < segments - 1) { 405 | l = segments - 1; 406 | while (i < l && (this._l2 = lengths[++i]) <= v) { } 407 | this._l1 = lengths[i-1]; 408 | this._li = i; 409 | this._curSeg = curSeg = this._segments[i]; 410 | this._s2 = curSeg[(this._s1 = this._si = 0)]; 411 | } else if (v < this._l1 && i > 0) { 412 | while (i > 0 && (this._l1 = lengths[--i]) >= v) { } 413 | if (i === 0 && v < this._l1) { 414 | this._l1 = 0; 415 | } else { 416 | i++; 417 | } 418 | this._l2 = lengths[i]; 419 | this._li = i; 420 | this._curSeg = curSeg = this._segments[i]; 421 | this._s1 = curSeg[(this._si = curSeg.length - 1) - 1] || 0; 422 | this._s2 = curSeg[this._si]; 423 | } 424 | curIndex = i; 425 | //now find the appropriate sub-segment (we split it into the number of pieces that was defined by "precision" and measured each one) 426 | v -= this._l1; 427 | i = this._si; 428 | if (v > this._s2 && i < curSeg.length - 1) { 429 | l = curSeg.length - 1; 430 | while (i < l && (this._s2 = curSeg[++i]) <= v) { } 431 | this._s1 = curSeg[i-1]; 432 | this._si = i; 433 | } else if (v < this._s1 && i > 0) { 434 | while (i > 0 && (this._s1 = curSeg[--i]) >= v) { } 435 | if (i === 0 && v < this._s1) { 436 | this._s1 = 0; 437 | } else { 438 | i++; 439 | } 440 | this._s2 = curSeg[i]; 441 | this._si = i; 442 | } 443 | t = ((i + (v - this._s1) / (this._s2 - this._s1)) * this._prec) || 0; 444 | } 445 | inv = 1 - t; 446 | 447 | i = this._props.length; 448 | while (--i > -1) { 449 | p = this._props[i]; 450 | b = this._beziers[p][curIndex]; 451 | val = (t * t * b.da + 3 * inv * (t * b.ca + inv * b.ba)) * t + b.a; 452 | if (this._mod[p]) { 453 | val = this._mod[p](val, target); 454 | } 455 | if (func[p]) { 456 | target[p](val); 457 | } else { 458 | target[p] = val; 459 | } 460 | } 461 | 462 | if (this._autoRotate) { 463 | var ar = this._autoRotate, 464 | b2, x1, y1, x2, y2, add, conv; 465 | i = ar.length; 466 | while (--i > -1) { 467 | p = ar[i][2]; 468 | add = ar[i][3] || 0; 469 | conv = (ar[i][4] === true) ? 1 : _RAD2DEG; 470 | b = this._beziers[ar[i][0]]; 471 | b2 = this._beziers[ar[i][1]]; 472 | 473 | if (b && b2) { //in case one of the properties got overwritten. 474 | b = b[curIndex]; 475 | b2 = b2[curIndex]; 476 | 477 | x1 = b.a + (b.b - b.a) * t; 478 | x2 = b.b + (b.c - b.b) * t; 479 | x1 += (x2 - x1) * t; 480 | x2 += ((b.c + (b.d - b.c) * t) - x2) * t; 481 | 482 | y1 = b2.a + (b2.b - b2.a) * t; 483 | y2 = b2.b + (b2.c - b2.b) * t; 484 | y1 += (y2 - y1) * t; 485 | y2 += ((b2.c + (b2.d - b2.c) * t) - y2) * t; 486 | 487 | val = notStart ? Math.atan2(y2 - y1, x2 - x1) * conv + add : this._initialRotations[i]; 488 | 489 | if (this._mod[p]) { 490 | val = this._mod[p](val, target); //for modProps 491 | } 492 | 493 | if (func[p]) { 494 | target[p](val); 495 | } else { 496 | target[p] = val; 497 | } 498 | } 499 | } 500 | } 501 | } 502 | }), 503 | p = BezierPlugin.prototype; 504 | 505 | 506 | BezierPlugin.bezierThrough = bezierThrough; 507 | BezierPlugin.cubicToQuadratic = cubicToQuadratic; 508 | BezierPlugin._autoCSS = true; //indicates that this plugin can be inserted into the "css" object using the autoCSS feature of TweenLite 509 | BezierPlugin.quadraticToCubic = function(a, b, c) { 510 | return new Segment(a, (2 * b + a) / 3, (2 * b + c) / 3, c); 511 | }; 512 | 513 | BezierPlugin._cssRegister = function() { 514 | var CSSPlugin = _globals.CSSPlugin; 515 | if (!CSSPlugin) { 516 | return; 517 | } 518 | var _internals = CSSPlugin._internals, 519 | _parseToProxy = _internals._parseToProxy, 520 | _setPluginRatio = _internals._setPluginRatio, 521 | CSSPropTween = _internals.CSSPropTween; 522 | _internals._registerComplexSpecialProp("bezier", {parser:function(t, e, prop, cssp, pt, plugin) { 523 | if (e instanceof Array) { 524 | e = {values:e}; 525 | } 526 | plugin = new BezierPlugin(); 527 | var values = e.values, 528 | l = values.length - 1, 529 | pluginValues = [], 530 | v = {}, 531 | i, p, data; 532 | if (l < 0) { 533 | return pt; 534 | } 535 | for (i = 0; i <= l; i++) { 536 | data = _parseToProxy(t, values[i], cssp, pt, plugin, (l !== i)); 537 | pluginValues[i] = data.end; 538 | } 539 | for (p in e) { 540 | v[p] = e[p]; //duplicate the vars object because we need to alter some things which would cause problems if the user plans to reuse the same vars object for another tween. 541 | } 542 | v.values = pluginValues; 543 | pt = new CSSPropTween(t, "bezier", 0, 0, data.pt, 2); 544 | pt.data = data; 545 | pt.plugin = plugin; 546 | pt.setRatio = _setPluginRatio; 547 | if (v.autoRotate === 0) { 548 | v.autoRotate = true; 549 | } 550 | if (v.autoRotate && !(v.autoRotate instanceof Array)) { 551 | i = (v.autoRotate === true) ? 0 : Number(v.autoRotate); 552 | v.autoRotate = (data.end.left != null) ? [["left","top","rotation",i,false]] : (data.end.x != null) ? [["x","y","rotation",i,false]] : false; 553 | } 554 | if (v.autoRotate) { 555 | if (!cssp._transform) { 556 | cssp._enableTransforms(false); 557 | } 558 | data.autoRotate = cssp._target._gsTransform; 559 | data.proxy.rotation = data.autoRotate.rotation || 0; 560 | cssp._overwriteProps.push("rotation"); 561 | } 562 | plugin._onInitTween(data.proxy, v, cssp._tween); 563 | return pt; 564 | }}); 565 | }; 566 | 567 | p._mod = function(lookup) { 568 | var op = this._overwriteProps, 569 | i = op.length, 570 | val; 571 | while (--i > -1) { 572 | val = lookup[op[i]]; 573 | if (val && typeof(val) === "function") { 574 | this._mod[op[i]] = val; 575 | } 576 | } 577 | }; 578 | 579 | p._kill = function(lookup) { 580 | var a = this._props, 581 | p, i; 582 | for (p in this._beziers) { 583 | if (p in lookup) { 584 | delete this._beziers[p]; 585 | delete this._func[p]; 586 | i = a.length; 587 | while (--i > -1) { 588 | if (a[i] === p) { 589 | a.splice(i, 1); 590 | } 591 | } 592 | } 593 | } 594 | a = this._autoRotate; 595 | if (a) { 596 | i = a.length; 597 | while (--i > -1) { 598 | if (lookup[a[i][2]]) { 599 | a.splice(i, 1); 600 | } 601 | } 602 | } 603 | return this._super._kill.call(this, lookup); 604 | }; 605 | 606 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 607 | 608 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 609 | (function(name) { 610 | "use strict"; 611 | var getGlobal = function() { 612 | return (_gsScope.GreenSockGlobals || _gsScope)[name]; 613 | }; 614 | if (typeof(module) !== "undefined" && module.exports) { //node 615 | require("../TweenLite.js"); 616 | module.exports = getGlobal(); 617 | } else if (typeof(define) === "function" && define.amd) { //AMD 618 | define(["TweenLite"], getGlobal); 619 | } 620 | }("BezierPlugin")); 621 | --------------------------------------------------------------------------------