• 原生 js 代码实现滚动条惯性效果


    依赖内裤:

       1 "use strict";
       2 
       3 var __emptyPoint = null, __emptyContext = null, __emptyPointA = null;
       4 
       5 const ColorRefTable = {
       6     "aliceblue": "#f0f8ff",
       7     "antiquewhite": "#faebd7",
       8     "aqua": "#00ffff",
       9     "aquamarine": "#7fffd4",
      10     "azure": "#f0ffff",
      11     "beige": "#f5f5dc",
      12     "bisque": "#ffe4c4",
      13     "black": "#000000",
      14     "blanchedalmond": "#ffebcd",
      15     "blue": "#0000ff",
      16     "blueviolet": "#8a2be2",
      17     "brown": "#a52a2a",
      18     "burlywood": "#deb887",
      19     "cadetblue": "#5f9ea0",
      20     "chartreuse": "#7fff00",
      21     "chocolate": "#d2691e",
      22     "coral": "#ff7f50",
      23     "cornsilk": "#fff8dc",
      24     "crimson": "#dc143c",
      25     "cyan": "#00ffff",
      26     "darkblue": "#00008b",
      27     "darkcyan": "#008b8b",
      28     "darkgoldenrod": "#b8860b",
      29     "darkgray": "#a9a9a9",
      30     "darkgreen": "#006400",
      31     "darkgrey": "#a9a9a9",
      32     "darkkhaki": "#bdb76b",
      33     "darkmagenta": "#8b008b",
      34     "firebrick": "#b22222",
      35     "darkolivegreen": "#556b2f",
      36     "darkorange": "#ff8c00",
      37     "darkorchid": "#9932cc",
      38     "darkred": "#8b0000",
      39     "darksalmon": "#e9967a",
      40     "darkseagreen": "#8fbc8f",
      41     "darkslateblue": "#483d8b",
      42     "darkslategray": "#2f4f4f",
      43     "darkslategrey": "#2f4f4f",
      44     "darkturquoise": "#00ced1",
      45     "darkviolet": "#9400d3",
      46     "deeppink": "#ff1493",
      47     "deepskyblue": "#00bfff",
      48     "dimgray": "#696969",
      49     "dimgrey": "#696969",
      50     "dodgerblue": "#1e90ff",
      51     "floralwhite": "#fffaf0",
      52     "forestgreen": "#228b22",
      53     "fuchsia": "#ff00ff",
      54     "gainsboro": "#dcdcdc",
      55     "ghostwhite": "#f8f8ff",
      56     "gold": "#ffd700",
      57     "goldenrod": "#daa520",
      58     "gray": "#808080",
      59     "green": "#008000",
      60     "greenyellow": "#adff2f",
      61     "grey": "#808080",
      62     "honeydew": "#f0fff0",
      63     "hotpink": "#ff69b4",
      64     "indianred": "#cd5c5c",
      65     "indigo": "#4b0082",
      66     "ivory": "#fffff0",
      67     "khaki": "#f0e68c",
      68     "lavender": "#e6e6fa",
      69     "lavenderblush": "#fff0f5",
      70     "lawngreen": "#7cfc00",
      71     "lemonchiffon": "#fffacd",
      72     "lightblue": "#add8e6",
      73     "lightcoral": "#f08080",
      74     "lightcyan": "#e0ffff",
      75     "lightgoldenrodyellow": "#fafad2",
      76     "lightgray": "#d3d3d3",
      77     "lightgreen": "#90ee90",
      78     "lightgrey": "#d3d3d3",
      79     "lightpink": "#ffb6c1",
      80     "lightsalmon": "#ffa07a",
      81     "lightseagreen": "#20b2aa",
      82     "lightskyblue": "#87cefa",
      83     "lightslategray": "#778899",
      84     "lightslategrey": "#778899",
      85     "lightsteelblue": "#b0c4de",
      86     "lightyellow": "#ffffe0",
      87     "lime": "#00ff00",
      88     "limegreen": "#32cd32",
      89     "linen": "#faf0e6",
      90     "magenta": "#ff00ff",
      91     "maroon": "#800000",
      92     "mediumaquamarine": "#66cdaa",
      93     "mediumblue": "#0000cd",
      94     "mediumorchid": "#ba55d3",
      95     "mediumpurple": "#9370db",
      96     "mediumseagreen": "#3cb371",
      97     "mediumslateblue": "#7b68ee",
      98     "mediumspringgreen": "#00fa9a",
      99     "mediumturquoise": "#48d1cc",
     100     "mediumvioletred": "#c71585",
     101     "midnightblue": "#191970",
     102     "mintcream": "#f5fffa",
     103     "mistyrose": "#ffe4e1",
     104     "moccasin": "#ffe4b5",
     105     "navajowhite": "#ffdead",
     106     "navy": "#000080",
     107     "oldlace": "#fdf5e6",
     108     "olive": "#808000",
     109     "olivedrab": "#6b8e23",
     110     "orange": "#ffa500",
     111     "orangered": "#ff4500",
     112     "orchid": "#da70d6",
     113     "palegoldenrod": "#eee8aa",
     114     "palegreen": "#98fb98",
     115     "paleturquoise": "#afeeee",
     116     "palevioletred": "#db7093",
     117     "papayawhip": "#ffefd5",
     118     "peachpuff": "#ffdab9",
     119     "peru": "#cd853f",
     120     "pink": "#ffc0cb",
     121     "plum": "#dda0dd",
     122     "powderblue": "#b0e0e6",
     123     "purple": "#800080",
     124     "red": "#ff0000",
     125     "rosybrown": "#bc8f8f",
     126     "royalblue": "#4169e1",
     127     "saddlebrown": "#8b4513",
     128     "salmon": "#fa8072",
     129     "sandybrown": "#f4a460",
     130     "seagreen": "#2e8b57",
     131     "seashell": "#fff5ee",
     132     "sienna": "#a0522d",
     133     "silver": "#c0c0c0",
     134     "skyblue": "#87ceeb",
     135     "slateblue": "#6a5acd",
     136     "slategray": "#708090",
     137     "slategrey": "#708090",
     138     "snow": "#fffafa",
     139     "springgreen": "#00ff7f",
     140     "steelblue": "#4682b4",
     141     "tan": "#d2b48c",
     142     "teal": "#008080",
     143     "thistle": "#d8bfd8",
     144     "tomato": "#ff6347",
     145     "turquoise": "#40e0d0",
     146     "violet": "#ee82ee",
     147     "wheat": "#f5deb3",
     148     "white": "#ffffff",
     149     "whitesmoke": "#f5f5f5",
     150     "yellow": "#ffff00",
     151     "yellowgreen": "#9acd32"
     152 },
     153 
     154 UTILS = {
     155 
     156     emptyArray(arr){
     157         return !Array.isArray(arr) || arr.length === 0;
     158     },
     159 
     160     createContext(w = 1, h = 1){
     161         const canvas = document.createElement("canvas"),
     162         context = canvas.getContext("2d");
     163         canvas.width = w;
     164         canvas.height = h;
     165         return context;
     166     },
     167 
     168     isObject(obj){
     169         
     170         return obj !== null && typeof obj === "object" && Array.isArray(obj) === false;
     171         
     172     },
     173     
     174     isNumber(num){
     175 
     176         return typeof num === "number" && isNaN(num) === false;
     177 
     178     },
     179 
     180     //获取最后一个点后面的字符
     181     getFileType(string){
     182         let type = "", str = string.split('').reverse().join('');
     183         for(let k = 0, len = str.length; k < len; k++){
     184             if(str[k] === ".") break;
     185             type += str[k];
     186         }
     187         return type.split('').reverse().join('');
     188     },
     189 
     190     //删除 string 所有的空格
     191     deleteSpaceAll(str){
     192         const len = str.length;
     193         var result = '';
     194         for(let i = 0; i < len; i++){
     195             if(str[i] !== '') result += str[i]
     196         }
     197 
     198         return result
     199     },
     200 
     201     //删除 string 两边空格
     202     removeSpaceSides(string){
     203 
     204         return string.replace(/(^\s*)|(\s*$)/g, "");
     205 
     206     },
     207 
     208     //返回 num 与 num1 之间的随机数
     209     random(num, num1){
     210         
     211         if(num < num1) return Math.random() * (num1 - num) + num;
     212 
     213         else if(num > num1) return Math.random() * (num - num1) + num1;
     214 
     215         else return num;
     216         
     217     },
     218 
     219     //生成 UUID
     220     generateUUID: function (){
     221         const _lut = [];
     222     
     223         for ( let i = 0; i < 256; i ++ ) {
     224     
     225             _lut[ i ] = ( i < 16 ? '0' : '' ) + ( i ).toString( 16 );
     226     
     227         }
     228     
     229         return function (){
     230             const d0 = Math.random() * 0xffffffff | 0;
     231             const d1 = Math.random() * 0xffffffff | 0;
     232             const d2 = Math.random() * 0xffffffff | 0;
     233             const d3 = Math.random() * 0xffffffff | 0;
     234             const uuid = _lut[ d0 & 0xff ] + _lut[ d0 >> 8 & 0xff ] + _lut[ d0 >> 16 & 0xff ] + _lut[ d0 >> 24 & 0xff ] + '-' +
     235             _lut[ d1 & 0xff ] + _lut[ d1 >> 8 & 0xff ] + '-' + _lut[ d1 >> 16 & 0x0f | 0x40 ] + _lut[ d1 >> 24 & 0xff ] + '-' +
     236             _lut[ d2 & 0x3f | 0x80 ] + _lut[ d2 >> 8 & 0xff ] + '-' + _lut[ d2 >> 16 & 0xff ] + _lut[ d2 >> 24 & 0xff ] +
     237             _lut[ d3 & 0xff ] + _lut[ d3 >> 8 & 0xff ] + _lut[ d3 >> 16 & 0xff ] + _lut[ d3 >> 24 & 0xff ];
     238     
     239             return uuid.toLowerCase(); //toLowerCase() 这里展平连接的字符串以节省堆内存空间
     240         }
     241     }(),
     242 
     243     //欧几里得距离(两点的直线距离)
     244     distance(x, y, x1, y1){
     245         
     246         return Math.sqrt(Math.pow(x - x1, 2) + Math.pow(y - y1, 2));
     247 
     248     },
     249 
     250     downloadFile(blob, fileName){
     251         const link = document.createElement("a");
     252         link.href = URL.createObjectURL(blob);
     253         link.download = fileName;
     254         link.click();
     255     },
     256 
     257     loadFileJSON(callback){
     258         const input = document.createElement("input");
     259         input.type = "file";
     260         input.accept = ".json";
     261         
     262         input.onchange = a => {
     263             if(a.target.files.length === 0) return;
     264             const fr = new FileReader();
     265             fr.onloadend = b => callback(b.target.result);
     266             fr.readAsText(a.target.files[0]); //fr.readAsDataURL(a.target.files[0]);
     267         }
     268         
     269         input.click();
     270     },
     271 
     272     get emptyPoint(){
     273         if(__emptyPoint === null) __emptyPoint = new Point();
     274         return __emptyPoint;
     275     },
     276 
     277     get emptyPointA(){
     278         if(__emptyPointA === null) __emptyPointA = new Point();
     279         return __emptyPointA;
     280     },
     281 
     282     get emptyContext(){
     283         if(__emptyContext === null) __emptyContext = document.createElement("canvas").getContext('2d')
     284         return __emptyContext;
     285     },
     286 
     287 }
     288 
     289 
     290 
     291 /* AnimateLoop 动画循环
     292 
     293 */
     294 class AnimateLoop{
     295 
     296     #id = NaN;
     297     #animate = null;
     298 
     299     get running(){
     300         return !isNaN(this.#id);
     301     }
     302 
     303     constructor(onupdate){
     304 
     305         const animate = () => {
     306             this.#id = requestAnimationFrame(animate);
     307             this.onupdate();
     308         }
     309 
     310         this.#animate = animate;
     311         this.onupdate = null;
     312 
     313         this.start(onupdate);
     314         
     315     }
     316 
     317     stop(){
     318         if(!isNaN(this.#id)){
     319             cancelAnimationFrame(this.#id);
     320             this.#id = NaN;
     321         }
     322     }
     323 
     324     start(onupdate){
     325         if(typeof onupdate === "function") this.onupdate = onupdate;
     326         if(this.onupdate !== null && isNaN(this.#id)){
     327             this.#animate();
     328         }
     329     }
     330 
     331 }
     332 
     333 
     334 
     335 
     336 /** Ajax
     337 parameter:
     338     option = {
     339         url:        可选, 默认 ''
     340         method:        可选, post 或 get请求, 默认 post
     341         asy:        可选, 是否异步执行, 默认 true
     342         success:    可选, 成功回调, 默认 null
     343         error:        可选, 超时或失败调用, 默认 null
     344         change:        可选, 请求状态改变时调用, 默认 null
     345         data:        可选, 如果定义则在初始化时自动执行.send(data)方法
     346     }
     347 
     348 demo:
     349     const data = `email=${email}&password=${password}`,
     350 
     351     //默认 post 请求:
     352     ajax = new Ajax({
     353         url: './login',
     354         data: data,
     355         success: mes => console.log(mes),
     356     });
     357     
     358     //get 请求:
     359     ajax.method = "get";
     360     ajax.send(data);
     361 */
     362 class Ajax{
     363     
     364     constructor(option = {}){
     365         this.url = option.url || "";
     366         this.method = option.method || "post";
     367         this.asy = typeof option.asy === "boolean" ? option.asy : true;
     368         this.success = option.success || null;
     369         this.error = option.error || null;
     370         this.change = option.change || null;
     371 
     372         //init XML
     373         this.xhr = new XMLHttpRequest();
     374 
     375         this.xhr.onerror = this.xhr.ontimeout = option.error || null;
     376 
     377         this.xhr.onreadystatechange = event => {
     378         
     379             if(event.target.readyState === 4 && event.target.status === 200){
     380 
     381                 if(this.success !== null) this.success(event.target.responseText, event);
     382                 
     383             }
     384 
     385             else if(this.change !== null) this.change(event);
     386 
     387         }
     388 
     389         if(option.data) this.send(option.data);
     390     }
     391 
     392     send(data = ""){
     393         if(this.method === "get"){
     394             this.xhr.open(this.method, this.url+"?"+data, this.asy);
     395             this.xhr.send();
     396         }
     397         
     398         else if(this.method === "post"){
     399             this.xhr.open(this.method, this.url, this.asy);
     400             this.xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
     401             this.xhr.send(data);
     402         }
     403     }
     404     
     405 }
     406 
     407 
     408 
     409 
     410 /* IndexedDB 本地数据库
     411 
     412 parameter:
     413     name: String;                //需要打开的数据库名称(如果不存在则会新建一个) 必须
     414     done: Function(IndexedDB);    //链接数据库成功时的回调 默认 null
     415     version: Number;             //数据库版本(高版本数据库将覆盖低版本的数据库) 默认 1 
     416 
     417 attribute:
     418     database: IndexedDB;            //链接完成的数据库对象
     419     transaction: IDBTransaction;    //事务管理(读和写)
     420     objectStore: IDBObjectStore;    //当前的事务
     421 
     422 method:
     423     set(data, key, callback)        //添加或更新
     424     get(key, callback)                //获取
     425     delete(key, callback)            //删除
     426 
     427     traverse(callback)                //遍历
     428     getAll(callback)                //获取全部
     429     clear(callback)                    //清理所以数据
     430     close()                         //关闭数据库链接
     431 
     432 readOnly:
     433 
     434 static:
     435     indexedDB: Object;
     436 
     437 demo:
     438     
     439     new IndexedDB('TEST', db => {
     440 
     441         conosle.log(db);
     442 
     443     });
     444 
     445 */
     446 class IndexedDB{
     447 
     448     static indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
     449 
     450     get objectStore(){ //每个事务只能使用一次, 所以每次都需要重新创建
     451         return this.database.transaction(this.name, 'readwrite').objectStore(this.name);
     452     }
     453 
     454     constructor(name, done = null, version = 1){
     455 
     456         if(IndexedDB.indexedDB === undefined) return console.error("IndexedDB: 不支持IndexedDB");
     457         
     458         if(typeof name !== 'string') return console.warn('IndexedDB: 参数错误');
     459 
     460         this.name = name;
     461         this.database = null;
     462 
     463         const request = IndexedDB.indexedDB.open(name, version);
     464         
     465         request.onupgradeneeded = (e)=>{ //数据库不存在 或 版本号不同时 触发
     466             if(!this.database) this.database = e.target.result;
     467             if(this.database.objectStoreNames.contains(name) === false) this.database.createObjectStore(name);
     468         }
     469         
     470         request.onsuccess = (e)=>{
     471             this.database = e.target.result;
     472             if(typeof done === 'function') done(this);
     473         }
     474         
     475         request.onerror = (e)=>{
     476             console.error(e);
     477         }
     478         
     479     }
     480 
     481     close(){
     482 
     483         return this.database.close();
     484 
     485     }
     486 
     487     clear(callback){
     488         
     489         this.objectStore.clear().onsuccess = callback;
     490         
     491     }
     492 
     493     traverse(callback){
     494         
     495         this.objectStore.openCursor().onsuccess = callback;
     496 
     497     }
     498 
     499     set(data, key = 0, callback){
     500         
     501         this.objectStore.put(data, key).onsuccess = callback;
     502 
     503     }
     504     
     505     get(key = 0, callback){
     506 
     507         this.objectStore.get(key).onsuccess = callback;
     508         
     509     }
     510 
     511     del(key = 0, callback){
     512 
     513         this.objectStore.delete(key).onsuccess = callback;
     514 
     515     }
     516     
     517     getAll(callback){
     518 
     519         this.objectStore.getAll().onsuccess = callback;
     520 
     521     }
     522 
     523 }
     524 
     525 
     526 
     527 
     528 /* TreeStruct 树结构基类
     529 
     530 attribute:
     531     parent: TreeStruct;
     532     children: Array[TreeStruct];
     533 
     534 method:
     535     add(v: TreeStruct): v;         //v添加到自己的子集
     536     remove(v: TreeStruct): v;     //删除v, 前提v必须是自己的子集
     537     export(): Array[Object];    //TreeStruct 转为 可导出的结构, 包括其所有的后代
     538 
     539     getPath(v: TreeStruct): Array[TreeStruct];     //获取自己到v的路径
     540 
     541     traverse(callback: Function): undefined;  //迭代自己的每一个后代, 包括自己
     542         callback(value: TreeStruct); //如返回 "continue" 则不在迭代其后代(不是结束迭代, 而是只结束当前节点的后代);
     543 
     544     traverseUp(callback): undefined; //向上遍历每一个父, 包括自己
     545         callback(value: TreeStruct); //如返回 "break" 立即停止遍历;
     546 
     547 static:
     548     import(arr: Array[Object]): TreeStruct; //.export() 返回的 arr 转为 TreeStruct
     549 
     550 */
     551 class TreeStruct{
     552 
     553     static import(arr){
     554 
     555         //json = JSON.parse(json);
     556         const len = arr.length;
     557 
     558         for(let k = 0, v; k < len; k++){
     559             v = Object.assign(new TreeStruct(), arr[k]);
     560             v.parent = arr[arr[k].parent] || null;
     561             if(v.parent !== null) v.parent.add(v);
     562             arr[k] = v;
     563         }
     564 
     565         return arr[0];
     566 
     567     }
     568 
     569     constructor(){
     570         this.parent = null;
     571         this.children = [];
     572     }
     573 
     574     getPath(v){
     575 
     576         var path;
     577 
     578         const pathA = [];
     579         this.traverseUp(tar => {
     580             if(v === tar){
     581                 path = pathA;
     582                 return "break";
     583             }
     584             pathA.push(tar);
     585         });
     586 
     587         if(path) return path;
     588 
     589         const pathB = [];
     590         v.traverseUp(tar => {
     591             if(this === tar){
     592                 path = pathB.reverse();
     593                 return "break";
     594             }
     595             else{
     596                 let i = pathA.indexOf(tar);
     597                 if(i !== -1){
     598                     pathA.splice(i);
     599                     pathA.push(tar);
     600                     path = pathA.concat(pathB.reverse());
     601                     return "break";
     602                 }
     603             }
     604             pathB.push(tar);
     605         });
     606 
     607         return path;
     608         
     609     }
     610 
     611     add(v){
     612         v.parent = this;
     613         if(this.children.includes(v) === false) this.children.push(v);
     614         
     615         return v;
     616     }
     617 
     618     remove(v){
     619         const i = this.children.indexOf(v);
     620         if(i !== -1) this.children.splice(i, 1);
     621         v.parent = null;
     622 
     623         return v;
     624     }
     625 
     626     traverse(callback, key = 0){
     627 
     628         if(callback(this, key) !== "continue"){
     629 
     630             for(let k = 0, len = this.children.length; k < len; k++){
     631 
     632                 this.children[k].traverse(callback, k);
     633     
     634             }
     635 
     636         }
     637 
     638     }
     639 
     640     traverseUp(callback){
     641 
     642         var par = this.parent;
     643 
     644         while(par !== null){
     645             if(callback(par) === "break") return;
     646             par = par.parent;
     647         }
     648 
     649     }
     650 
     651     export(){
     652 
     653         const result = [], arr = [];
     654         var obj = null;
     655 
     656         this.traverse(v => {
     657             obj = Object.assign({}, v);
     658             obj.parent = arr.indexOf(v.parent);
     659             delete obj.children;
     660             result.push(obj);
     661             arr.push(v);
     662         });
     663         
     664         return result; //JSON.stringify(result);
     665 
     666     }
     667 
     668 }
     669 
     670 Object.defineProperties(TreeStruct.prototype, {
     671     
     672     isTreeStruct: {
     673         configurable: false,
     674         enumerable: false,
     675         writable: false,
     676         value: true,
     677     }
     678 
     679 });
     680 
     681 
     682 
     683 
     684 /* Point
     685 parameter: 
     686     x = 0, y = 0;
     687 
     688 attribute
     689     x, y: Number;
     690 
     691 method:
     692     set(x, y): this;
     693     angle(origin): Number;
     694     copy(point): this;
     695     clone(): Point;
     696     distance(point): Number;            //获取欧几里得距离
     697     distanceMHD(point): Number;            //获取曼哈顿距离
     698     distanceCompare(point): Number;        //获取用于比较的距离(相对于.distance() 效率更高)
     699     equals(point): Bool;                //是否恒等
     700     reverse(): this;                    //取反值
     701     rotate(origin: Object{x,y}, angle): this;    //旋转点
     702     normalize(): this;                    //归一
     703 */
     704 class Point{
     705 
     706     constructor(x = 0, y = 0){
     707         this.x = x;
     708         this.y = y;
     709     }
     710 
     711     set(x = 0, y = 0){
     712         this.x = x;
     713         this.y = y;
     714 
     715         return this;
     716     }
     717 
     718     angle(origin){
     719 
     720         return Math.atan2(this.y - origin.y, this.x - origin.x);
     721 
     722     }
     723 
     724     copy(point){
     725         
     726         this.x = point.x;
     727         this.y = point.y;
     728         return this;
     729         //return Object.assign(this, point);
     730 
     731     }
     732     
     733     clone(){
     734 
     735         return new this.constructor().copy(this);
     736         //return Object.assign(new this.constructor(), this);
     737         
     738     }
     739 
     740     distance(point){
     741         
     742         return Math.sqrt(Math.pow(point.x - this.x, 2) + Math.pow(point.y - this.y, 2));
     743 
     744     }
     745 
     746     distanceMHD(point){
     747 
     748         return Math.abs(this.x - point.x) + Math.abs(this.y - point.y);
     749 
     750     }
     751 
     752     distanceCompare(point){
     753     
     754         return Math.pow(this.x - point.x, 2) + Math.pow(this.y - point.y, 2);
     755 
     756     }
     757 
     758     equals(point){
     759 
     760         return point.x === this.x && point.y === this.y;
     761 
     762     }
     763 
     764     reverse(){
     765         this.x = -this.x;
     766         this.y = -this.y;
     767 
     768         return this;
     769     }
     770 
     771     rotate(origin, angle){
     772         const c = Math.cos(angle), s = Math.sin(angle), 
     773         x = this.x - origin.x, y = this.y - origin.y;
     774 
     775         this.x = x * c - y * s + origin.x;
     776         this.y = x * s + y * c + origin.y;
     777 
     778         return this;
     779     }
     780 
     781     normalize(){
     782         const len = 1 / (Math.sqrt(this.x * this.x + this.y * this.y) || 1);
     783         this.x *= len;
     784         this.y *= len;
     785 
     786         return this;
     787     }
     788 
     789 /*     add(point){
     790         this.x += point.x;
     791         this.y += point.y;
     792         return this;
     793     }
     794 
     795     sub(point){
     796         this.x -= point.x;
     797         this.y -= point.y;
     798         return this;
     799     }
     800 
     801     multiply(point){
     802         this.x *= point.x;
     803         this.y *= point.y;
     804         return this;
     805     }
     806 
     807     divide(point){
     808         this.x /= point.x;
     809         this.y /= point.y;
     810         return this;
     811     } */
     812 
     813 }
     814 
     815 Object.defineProperties(Point.prototype, {
     816 
     817     isPoint: {
     818         configurable: false,
     819         enumerable: false,
     820         writable: false,
     821         value: true,
     822     }
     823 
     824 });
     825 
     826 
     827 
     828 
     829 /* Rotate
     830 parameter:
     831     x, y, a = 0
     832 
     833 attribute:
     834     angle: Number;
     835     origin: Point;
     836 
     837 method:
     838     set(x, y, a): this;
     839 */
     840 class Rotate{
     841 
     842     constructor(x, y, a = 0){
     843         this.angle = a;
     844         this.origin = new Point(x, y);
     845     }
     846 
     847     set(x, y, a){
     848         this.origin.x = x;
     849         this.origin.y = y;
     850         this.angle = a;
     851         return this;
     852     }
     853 
     854     pos(x, y){
     855         this.origin.x = x;
     856         this.origin.y = y;
     857         return this;
     858     }
     859 
     860     copy(rotate){
     861         this.angle = rotate.angle;
     862         this.origin.copy(rotate.origin);
     863         return this;
     864     }
     865     
     866     clone(){
     867         return new this.constructor().copy(this);
     868     }
     869 
     870     toAngle(v){
     871         this.angle = v / 180 * Math.PI;
     872         return this;
     873     }
     874 
     875 }
     876 
     877 
     878 
     879 
     880 /* Line
     881 parameter: x, y, x1, y1: Number;
     882 attribute: x, y, x1, y1: Number;
     883 method:
     884     set(x, y, x1, y1): this;                    
     885     copy(line): this;
     886     clone(): Line;
     887     containsPoint(x, y): Bool;                             //点是否在线上
     888     intersectPoint(line: Line, point: Point): Point;    //如果不相交则返回null, 否则返回交点Point
     889     isIntersect(line): Bool;                             //this与line是否相交
     890 */
     891 class Line{
     892 
     893     constructor(x = 0, y = 0, x1 = 0, y1 = 0){
     894         this.x = x;
     895         this.y = y;
     896         this.x1 = x1;
     897         this.y1 = y1;
     898     }
     899 
     900     set(x = 0, y = 0, x1 = 0, y1 = 0){
     901         this.x = x;
     902         this.y = y;
     903         this.x1 = x1;
     904         this.y1 = y1;
     905         return this;
     906     }
     907 
     908     copy(line){
     909         this.x = line.x;
     910         this.y = line.y;
     911         this.x1 = line.x1;
     912         this.y1 = line.y1;
     913         return this;
     914         //return Object.assign(this, line);
     915     }
     916     
     917     clone(){
     918         return new this.constructor().copy(this);
     919         //return Object.assign(new this.constructor(), this);
     920     }
     921 
     922     containsPoint(x, y){
     923         return (x - this.x) * (x - this.x1) <= 0 && (y - this.y) * (y - this.y1) <= 0;
     924     }
     925 
     926     intersectPoint(line, point){
     927         //解线性方程组, 求线段交点
     928         //如果分母为0则平行或共线, 不相交
     929         var denominator = (this.y1 - this.y) * (line.x1 - line.x) - (this.x - this.x1) * (line.y - line.y1);
     930         if(denominator === 0) return null;
     931 
     932         //线段所在直线的交点坐标 (x , y)
     933         const x = ((this.x1 - this.x) * (line.x1 - line.x) * (line.y - this.y) 
     934         + (this.y1 - this.y) * (line.x1 - line.x) * this.x 
     935         - (line.y1 - line.y) * (this.x1 - this.x) * line.x) / denominator;
     936 
     937         const y = -((this.y1 - this.y) * (line.y1 - line.y) * (line.x - this.x) 
     938         + (this.x1 - this.x) * (line.y1 - line.y) * this.y 
     939         - (line.x1 - line.x) * (this.y1 - this.y) * line.y) / denominator;
     940 
     941         //判断交点是否在两条线段上
     942         if(this.containsPoint(x, y) && line.containsPoint(x, y)){
     943             point.x = x;
     944             point.y = y;
     945             return point;
     946         }
     947 
     948         return null;
     949     }
     950 
     951     isIntersect(line){
     952         //快速排斥:
     953         //两个线段为对角线组成的矩形,如果这两个矩形没有重叠的部分,那么两条线段是不可能出现重叠的
     954 
     955         //这里的确如此,这一步是判定两矩形是否相交
     956         //1.线段ab的低点低于cd的最高点(可能重合)
     957         //2.cd的最左端小于ab的最右端(可能重合)
     958         //3.cd的最低点低于ab的最高点(加上条件1,两线段在竖直方向上重合)
     959         //4.ab的最左端小于cd的最右端(加上条件2,两直线在水平方向上重合)
     960         //综上4个条件,两条线段组成的矩形是重合的
     961         //特别要注意一个矩形含于另一个矩形之内的情况
     962         if(!(Math.min(this.x,this.x1)<=Math.max(line.x,line.x1) && Math.min(line.y,line.y1)<=Math.max(this.y,this.y1) && Math.min(line.x,line.x1)<=Math.max(this.x,this.x1) && Math.min(this.y,this.y1)<=Math.max(line.y,line.y1))) return false;
     963 
     964         //跨立实验:
     965         //如果两条线段相交,那么必须跨立,就是以一条线段为标准,另一条线段的两端点一定在这条线段的两段
     966         //也就是说a b两点在线段cd的两端,c d两点在线段ab的两端
     967         var u=(line.x-this.x)*(this.y1-this.y)-(this.x1-this.x)*(line.y-this.y),
     968         v = (line.x1-this.x)*(this.y1-this.y)-(this.x1-this.x)*(line.y1-this.y),
     969         w = (this.x-line.x)*(line.y1-line.y)-(line.x1-line.x)*(this.y-line.y),
     970         z = (this.x1-line.x)*(line.y1-line.y)-(line.x1-line.x)*(this.y1-line.y);
     971         
     972         return u*v <= 0.00000001 && w*z <= 0.00000001;
     973     }
     974 
     975 }
     976 
     977 
     978 
     979 
     980 /* Box 矩形
     981 parameter: 
     982     x = 0, y = 0, w = 0, h = 0;
     983 
     984 attribute:
     985     x,y: Number; 位置
     986     w,h: Number; 大小
     987 
     988     只读
     989     mx, my: Number; //
     990 
     991 method:
     992     set(x, y, w, h): this;
     993     pos(x, y): this; //设置位置
     994     size(w, h): this; //设置大小
     995     setFromRotate(rotate: Rotate): this;        //根据 rotate 旋转自己
     996     setFromShapeRect(shapeRect): this;            //ShapeRect 转为 Box (忽略 ShapeRect 的旋转和缩放)
     997     setFromCircle(circle, inner: Bool): this;    //
     998     setFromPolygon(polygon, inner = true): this;//
     999     toArray(array: Array, index: Integer): this;
    1000     copy(box): this;                             //复制
    1001     clone(): Box;                                  //克隆
    1002     center(box): this;                            //设置位置在box居中
    1003     distance(x, y): Number;                     //左上角原点 与 x,y 的直线距离
    1004     isEmpty(): Boolean;                         //.w.h是否小于等于零
    1005     maxX(): Number;                             //返回 max x(this.x+this.w);
    1006     maxY(): Number;                             //返回 max y(this.y+this.h);
    1007     expand(box): undefined;                     //扩容; 把box合并到this
    1008     equals(box): Boolean;                         //this与box是否恒等
    1009     intersectsBox(box): Boolean;                 //box与this是否相交(box在this内部也会返回true)
    1010     containsPoint(x, y): Boolean;                 //x,y点是否在this内
    1011     containsBox(box): Boolean;                    //box是否在this内(只是相交返回fasle)
    1012     computeOverflow(b: Box, r: Box): undefined;    //this相对b超出的部分赋值到r; r的size小于或等于0的话说明完全超出
    1013 */
    1014 class Box{
    1015 
    1016     get mx(){
    1017         return this.x + this.w;
    1018     }
    1019 
    1020     get my(){
    1021         return this.y + this.h;
    1022     }
    1023 
    1024     get cx(){
    1025         return this.w / 2 + this.x;
    1026     }
    1027 
    1028     get cy(){
    1029         return this.h / 2 + this.y;
    1030     }
    1031 
    1032     constructor(x = 0, y = 0, w = 0, h = 0){
    1033         //this.set(x, y, w, h);
    1034         this.x = x;
    1035         this.y = y;
    1036         this.w = w;
    1037         this.h = h;
    1038     }
    1039     
    1040     set(x, y, w, h){
    1041         this.x = x;
    1042         this.y = y;
    1043         this.w = w;
    1044         this.h = h;
    1045         return this;
    1046     }
    1047 
    1048     pos(x, y){
    1049         this.x = x;
    1050         this.y = y;
    1051         return this;
    1052     }
    1053     
    1054     size(w, h){
    1055         this.w = w;
    1056         this.h = h;
    1057         return this;
    1058     }
    1059 
    1060     setFromRotate(rotate){
    1061         var minX = this.x, minY = this.y, maxX = 0, maxY = 0;
    1062         const point = UTILS.emptyPoint,
    1063         run = function (){
    1064             if(point.x < minX) minX = point.x;
    1065             else if(point.x > maxX) maxX = point.x;
    1066             if(point.y < minY) minY = point.y;
    1067             else if(point.y > maxY) maxY = point.y;
    1068         }
    1069 
    1070         point.set(this.x, this.y).rotate(rotate.origin, rotate.angle); run();
    1071         point.set(this.mx, this.y).rotate(rotate.origin, rotate.angle); run();
    1072         point.set(this.mx, this.my).rotate(rotate.origin, rotate.angle); run();
    1073         point.set(this.x, this.my).rotate(rotate.origin, rotate.angle); run();
    1074 
    1075         this.x = minX;
    1076         this.y = minY;
    1077         this.w = maxX - minX;
    1078         this.h = maxY - minY;
    1079 
    1080         return this;
    1081     }
    1082 
    1083     setFromShapeRect(shapeRect){
    1084         this.width = shapeRect.width;
    1085         this.height = shapeRect.height;
    1086         this.x = shapeRect.position.x - this.width / 2;
    1087         this.y = shapeRect.position.y - this.height / 2;
    1088         return this;
    1089     }
    1090 
    1091     setFromCircle(circle, inner = true){
    1092         if(inner === true){
    1093             this.x = Math.sin(-135 / 180 * Math.PI) * circle.r + circle.x;
    1094             this.y = Math.cos(-135 / 180 * Math.PI) * circle.r + circle.y;
    1095             this.w = this.h = Math.sin(135 / 180 * Math.PI) * circle.r + circle.x - this.x;
    1096         }
    1097 
    1098         else{
    1099             this.x = circle.x - circle.r;
    1100             this.y = circle.y - circle.r;
    1101             this.w = this.h = circle.r * 2;
    1102         }
    1103         return this;
    1104     }
    1105 
    1106     setFromPolygon(polygon, inner = true){
    1107         if(inner === true){
    1108             console.warn('Box: 暂不支持第二个参数为true');
    1109         }
    1110 
    1111         else{
    1112             const len = polygon.path.length;
    1113             let x = Infinity, y = Infinity, mx = 0, my = 0;
    1114             for(let k = 0, v; k < len; k+=2){
    1115                 v = polygon.path[k];
    1116                 if(v < x) x = v;
    1117                 else if(v > mx) mx = v;
    1118 
    1119                 v = polygon.path[k+1];
    1120                 if(v < y) y = v;
    1121                 else if(v > my) my = v;
    1122 
    1123             }
    1124 
    1125             this.set(x, y, mx - x, my - y);
    1126 
    1127         }
    1128         return this;
    1129     }
    1130 
    1131     toArray(array, index){
    1132         array[index] = this.x;
    1133         array[index+1] = this.y;
    1134         array[index+2] = this.w;
    1135         array[index+3] = this.h;
    1136 
    1137         return this;
    1138     }
    1139 
    1140     copy(box){
    1141         this.x = box.x;
    1142         this.y = box.y;
    1143         this.w = box.w;
    1144         this.h = box.h;
    1145         return this;
    1146         //return Object.assign(this, box); //this.set(box.x, box.y, box.w, box.h);
    1147     }
    1148     
    1149     clone(){
    1150         return new this.constructor().copy(this);
    1151         //return Object.assign(new this.constructor(), this);
    1152     }
    1153 
    1154     center(box){
    1155         this.x = (box.w - this.w) / 2 + box.x;
    1156         this.y = (box.h - this.h) / 2 + box.y;
    1157         return this;
    1158     }
    1159 
    1160     distance(x, y){
    1161         return Math.sqrt(Math.pow(this.x - x, 2) + Math.pow(this.y - y, 2));
    1162     }
    1163 
    1164     isEmpty(){
    1165         return this.w <= 0 || this.h <= 0;
    1166     }
    1167 
    1168     maxX(){
    1169         return this.x + this.w;
    1170     }
    1171 
    1172     maxY(){
    1173         return this.y + this.h;
    1174     }
    1175 
    1176     equals(box){
    1177         return this.x === box.x && this.w === box.w && this.y === box.y && this.h === box.h;
    1178     }
    1179 
    1180     expand(box){
    1181         var v = Math.min(this.x, box.x);
    1182         this.w = Math.max(this.x + this.w - v, box.x + box.w - v);
    1183         this.x = v;
    1184 
    1185         v = Math.min(this.y, box.y);
    1186         this.h = Math.max(this.y + this.h - v, box.y + box.h - v);
    1187         this.y = v;
    1188     }
    1189 
    1190     intersectsBox(box){
    1191         return box.x + box.w < this.x || box.x > this.x + this.w || box.y + box.h < this.y || box.y > this.y + this.h ? false : true;
    1192     }
    1193 
    1194     containsPoint(x, y){
    1195         return x < this.x || x > this.x + this.w || y < this.y || y > this.y + this.h ? false : true;
    1196     }
    1197 
    1198     containsBox(box){
    1199         return this.x <= box.x && box.x + box.w <= this.x + this.w && this.y <= box.y && box.y + box.h <= this.y + this.h;
    1200     }
    1201 
    1202     computeOverflow(p, r){
    1203         r["copy"](this);
    1204         
    1205         if(this["x"] < p["x"]){
    1206             r["x"] = p["x"];
    1207             r["w"] -= p["x"] - this["x"];
    1208         }
    1209 
    1210         if(this["y"] < p["y"]){
    1211             r["y"] = p["y"];
    1212             r["h"] -= p["y"] - this["y"];
    1213         }
    1214 
    1215         var m = p["x"] + p["w"];
    1216         if(r["x"] + r["w"] > m) r["w"] = m - r["x"];
    1217 
    1218         m = p["y"] + p["h"];
    1219         if(r["y"] + r["h"] > m) r["h"] = m - r["y"];
    1220     }
    1221     
    1222 }
    1223 
    1224 Object.defineProperties(Box.prototype, {
    1225 
    1226     isBox: {
    1227         configurable: false,
    1228         enumerable: false,
    1229         writable: false,
    1230         value: true,
    1231     },
    1232 
    1233 });
    1234 
    1235 
    1236 
    1237 
    1238 /* Circle 圆形
    1239 parameter:
    1240 attribute:
    1241     x,y: Number; 中心点
    1242     r: Number; 半径
    1243 
    1244     //只读
    1245     r2: Number; //返回直径 r*2
    1246 
    1247 method:
    1248     set(x, y, r): this;
    1249     pos(x, y): this;
    1250     copy(circle: Circle): this;
    1251     clone(): Circle;
    1252     distance(x, y): Number;
    1253     equals(circle: Circle): Bool;
    1254     containsPoint(x, y): Bool; 
    1255     intersectsCircle(circle: Circle): Bool;
    1256     intersectsBox(box: Box): Bool;
    1257     setFromBox(box, inner = true): this;
    1258 
    1259 */
    1260 class Circle{
    1261 
    1262     get r2(){
    1263         return this.r * 2;
    1264     }
    1265 
    1266     constructor(x = 0, y = 0, r = -1){
    1267         //this.set(0, 0, -1);
    1268         this.x = x;
    1269         this.y = y;
    1270         this.r = r;
    1271     }
    1272 
    1273     set(x, y, r){
    1274         this.x = x;
    1275         this.y = y;
    1276         this.r = r;
    1277 
    1278         return this;
    1279     }
    1280 
    1281     setFromBox(box, world = true, inner = true){
    1282         this.x = box.w / 2 + (world === true ? box.x : 0);
    1283         this.y = box.h / 2 + (world === true ? box.y : 0);
    1284         this.r = inner === true ? Math.min(box.w, box.h) / 2 : UTILS.distance(0, 0, box.w, box.h) / 2;
    1285     
    1286         return this;
    1287     }
    1288 
    1289     toArray(array, index){
    1290         array[index] = this.x;
    1291         array[index+1] = this.y;
    1292         array[index+2] = this.r;
    1293         
    1294         return this;
    1295     }
    1296 
    1297     pos(x, y){
    1298         this.x = x;
    1299         this.y = y;
    1300 
    1301         return this;
    1302     }
    1303 
    1304     copy(circle){
    1305         this.r = circle.r;
    1306         this.x = circle.x;
    1307         this.y = circle.y;
    1308 
    1309         return this;
    1310     }
    1311 
    1312     clone(){
    1313 
    1314         return new this.constructor().copy(this);
    1315 
    1316     }
    1317 
    1318     distance(x, y){
    1319         
    1320         return Math.sqrt(Math.pow(this.x - x, 2) + Math.pow(this.y - y, 2));
    1321 
    1322     }
    1323 
    1324     equals(circle){
    1325 
    1326         return circle.x === this.x && circle.y === this.y && circle.r === this.r;
    1327 
    1328     }
    1329 
    1330     containsPoint(x, y){
    1331 
    1332         return (Math.pow(x - this.x, 2) + Math.pow(y - this.y, 2) <= Math.pow(this.r, 2));
    1333 
    1334     }
    1335 
    1336     intersectsCircle(circle){
    1337 
    1338         return (Math.pow(circle.x - this.x, 2) + Math.pow(circle.y - this.y, 2) <= Math.pow(circle.r + this.r, 2));
    1339 
    1340     }
    1341 
    1342     intersectsBox(box){
    1343         
    1344         return (Math.pow(Math.max(box.x, Math.min(box.x + box.w, this.x)) - this.x, 2) + Math.pow(Math.max(box.y, Math.min(box.y + box.h, this.y)) - this.y, 2) <= Math.pow(this.r, 2));
    1345     
    1346     }
    1347 
    1348 }
    1349 
    1350 Object.defineProperties(Circle.prototype, {
    1351 
    1352     isCircle: {
    1353         configurable: false,
    1354         enumerable: false,
    1355         writable: false,
    1356         value: true,
    1357     }
    1358 
    1359 });
    1360 
    1361 
    1362 
    1363 
    1364 /* Polygon 多边形
    1365 
    1366 parameter: 
    1367     path: Array[x, y];
    1368 
    1369 attribute:
    1370 
    1371     //只读属性
    1372     path: Array[x, y]; 
    1373 
    1374 method:
    1375     add(x, y): this;             //x,y添加至path;
    1376     containsPoint(x, y): Bool;    //x,y是否在多边形的内部(注意: 在路径上也返回 true)
    1377     
    1378 */
    1379 class Polygon{
    1380 
    1381     #position = null;
    1382     #path2D = null;
    1383 
    1384     get path(){
    1385         
    1386         return this.#position;
    1387 
    1388     }
    1389 
    1390     constructor(path = []){
    1391         this.#position = path;
    1392 
    1393         this.#path2D = new Path2D();
    1394         
    1395         var len = path.length;
    1396         if(len >= 2){
    1397             if(len % 2 !== 0){
    1398                 len -= 1;
    1399                 path.splice(len, 1);
    1400             }
    1401 
    1402             const con = this.#path2D;
    1403             con.moveTo(path[0], path[1]);
    1404             for(let k = 2; k < len; k+=2) con.lineTo(path[k], path[k+1]);
    1405             
    1406         }
    1407 
    1408     }
    1409 
    1410     add(x, y){
    1411         this.#position.push(x, y);
    1412         this.#path2D.lineTo(x, y);
    1413         return this;
    1414     }
    1415 
    1416     containsPoint(x, y){
    1417         
    1418         return UTILS.emptyContext.isPointInPath(this.#path2D, x, y);
    1419 
    1420     }
    1421 
    1422     isInPolygon(checkPoint, polygonPoints) {
    1423         var counter = 0;
    1424         var i;
    1425         var xinters;
    1426         var p1, p2;
    1427         var pointCount = polygonPoints.length;
    1428         p1 = polygonPoints[0];
    1429         for (i = 1; i <= pointCount; i++) {
    1430             p2 = polygonPoints[i % pointCount];
    1431             if (
    1432                 checkPoint[0] > Math.min(p1[0], p2[0]) &&
    1433                 checkPoint[0] <= Math.max(p1[0], p2[0])
    1434             ) {
    1435                 if (checkPoint[1] <= Math.max(p1[1], p2[1])) {
    1436                     if (p1[0] != p2[0]) {
    1437                         xinters =
    1438                             (checkPoint[0] - p1[0]) *
    1439                                 (p2[1] - p1[1]) /
    1440                                 (p2[0] - p1[0]) +
    1441                             p1[1];
    1442                         if (p1[1] == p2[1] || checkPoint[1] <= xinters) {
    1443                             counter++;
    1444                         }
    1445                     }
    1446                 }
    1447             }
    1448             p1 = p2;
    1449         }
    1450         if (counter % 2 == 0) {
    1451             return false;
    1452         } else {
    1453             return true;
    1454         }
    1455     }
    1456 
    1457     containsPolygon(polygon){
    1458         const path = polygon.path, len = path.length;
    1459         for(let k = 0; k < len; k += 2){
    1460             if(this.containsPoint(path[k], path[k+1]) === false) return false;
    1461         }
    1462 
    1463         return true;
    1464     }
    1465 
    1466     toPoints(){
    1467         const path = this.path, len = path.length, result = [];
    1468         
    1469         for(let k = 0; k < len; k += 2) result.push(new Point(path[k], path[k+1]));
    1470 
    1471         return result;
    1472     }
    1473 
    1474     toLines(){
    1475         const path = this.path, len = path.length, result = [];
    1476         
    1477         for(let k = 0, x = NaN, y; k < len; k += 2){
    1478 
    1479             if(isNaN(x)){
    1480                 x = path[k];
    1481                 y = path[k+1];
    1482                 continue;
    1483             }
    1484 
    1485             const line = new Line(x, y, path[k], path[k+1]);
    1486             
    1487             x = line.x1;
    1488             y = line.y1;
    1489 
    1490             result.push(line);
    1491 
    1492         }
    1493 
    1494         return result;
    1495     }
    1496 
    1497     merge(polygon){
    1498 
    1499         const linesA = this.toLines(), linesB = polygon.toLines(), nodes = [], newLines = [],
    1500         
    1501         pointA = new Point(), pointB = new Point(),
    1502 
    1503         forEachNodes = (pathA, pathB, funcA = null, funcB = null) => {
    1504             for(let k = 0, lenA = pathA.length, lenB = pathB.length; k < lenA; k++){
    1505                 if(funcA !== null) funcA(pathA[k]);
    1506 
    1507                 for(let i = 0; i < lenB; i++){
    1508                     if(funcB !== null) funcB(pathB[i], pathA[k]);
    1509                 }
    1510     
    1511             }
    1512         }
    1513 
    1514         if(this.containsPolygon(polygon)){console.log('this -> polygon');
    1515             forEachNodes(linesA, linesB, lineA => newLines.push(lineA), (lineB, lineA) => {
    1516                 if(lineA.intersectPoint(lineB, pointA) === pointA) newLines.push(pointA.clone());
    1517             });
    1518 
    1519             return newLines;
    1520         }
    1521 
    1522         //收集所有的交点 (保存至 line)
    1523         forEachNodes(linesA, linesB, lineA => lineA.nodes = [], (lineB, lineA) => {
    1524             if(lineB.nodes === undefined) lineB.nodes = [];
    1525             if(lineA.intersectPoint(lineB, pointA) === pointA){
    1526                 const node = {
    1527                     lineA: lineA, 
    1528                     lineB: lineB, 
    1529                     point: pointA.clone(),
    1530                     disA: pointA.distanceCompare(pointB.set(lineA.x, lineA.y)),
    1531                     disB: pointA.distanceCompare(pointB.set(lineB.x, lineB.y)),
    1532                 }
    1533                 lineA.nodes.push(node);
    1534                 lineB.nodes.push(node);
    1535                 nodes.push(node);
    1536             }
    1537         });
    1538 
    1539         //交点以原点为目标排序
    1540         for(let k = 0, sotr = function (a,b){return a.disA - b.disA;}, countA = linesA.length; k < countA; k++) linesA[k].nodes.sort(sotr);
    1541         for(let k = 0, sotr = function (a,b){return a.disB - b.disB;}, countB = linesB.length; k < countB; k++) linesB[k].nodes.sort(sotr);
    1542 
    1543         var _loopTypeA, _loopTypeB;
    1544         const result_loop = {
    1545             lines: null,
    1546             loopType: '',
    1547             line: null,
    1548             count: 0,
    1549             indexed: 0,
    1550         },
    1551         
    1552         //遍历某条线
    1553         loop = (lines, index, loopType) => {
    1554             const length = lines.length, indexed = lines.length, model = lines === linesA ? polygon : this;
    1555         
    1556             var line, i = 1;
    1557             while(true){
    1558                 if(loopType === 'next') index = index === length - 1 ? 0 : index + 1;
    1559                 else if(loopType === 'back') index = index === 0 ? length - 1 : index - 1;
    1560                 line = lines[index];
    1561 
    1562                 result_loop.count = line.nodes.length;
    1563                 if(result_loop.count !== 0){
    1564                     result_loop.lines = lines;
    1565                     result_loop.loopType = loopType;
    1566                     result_loop.line = line;
    1567                     result_loop.indexed = index;
    1568                     if(loopType === 'next') addLine(line, model);
    1569 
    1570                     return result_loop;
    1571                 }
    1572                 
    1573                 addLine(line, model);
    1574                 if(indexed === i++) break;
    1575 
    1576             }
    1577             
    1578         },
    1579 
    1580         //更新或创建交点的索引
    1581         setNodeIndex = (lines, index, loopType) => {
    1582             const line = lines[index], count = line.nodes.length;
    1583             if(loopType === undefined) loopType = lines === linesA ? _loopTypeA : _loopTypeB;
    1584 
    1585             if(loopType === undefined) return;
    1586             
    1587             if(line.nodeIndex === undefined){
    1588                 line.nodeIndex = loopType === 'next' ? 0 : count - 1;
    1589                 line.nodeState = count === 1 ? 'end' : 'start';
    1590             
    1591             }
    1592 
    1593             else{
    1594                 if(line.nodeState === 'end' || line.nodeState === ''){
    1595                     line.nodeState = '';
    1596                     return;
    1597                 }
    1598 
    1599                 if(loopType === 'next'){
    1600                     line.nodeIndex += 1;
    1601 
    1602                     if(line.nodeIndex === count - 1) line.nodeState = 'end';
    1603                     else line.nodeState = 'run';
    1604                 }
    1605 
    1606                 else if(loopType === 'back'){
    1607                     line.nodeIndex -= 1;
    1608 
    1609                     if(line.nodeIndex === 0) line.nodeState = 'end';
    1610                     else line.nodeState = 'run';
    1611                 }
    1612 
    1613             }
    1614 
    1615         },
    1616 
    1617         //只有在跳线的时候才执行此方法, 如果跳线的话: 某条线上的交点必然在两端;
    1618         getLoopType = (lines, index, nodePoint) => {
    1619             const line = lines[index], lineNext = lines[index === lines.length - 1 ? 0 : index + 1],
    1620 
    1621             model = lines === linesA ? polygon : this,
    1622             isLineBack = newLines.includes(line) === false && model.containsPoint(line.x, line.y) === false,
    1623             isLineNext = newLines.includes(lineNext) === false && model.containsPoint(lineNext.x, lineNext.y) === false;
    1624             
    1625             if(isLineBack && isLineNext){
    1626                 const len = line.nodes.length;
    1627                 if(len >= 2){
    1628                     if(line.nodes[len - 1].point.equals(nodePoint)) return 'next';
    1629                     else if(line.nodes[0].point.equals(nodePoint)) return 'back';
    1630                 }
    1631                 
    1632                 else console.warn('路径复杂', line);
    1633                 
    1634             }
    1635 
    1636             else if(isLineNext){
    1637                 return 'next';
    1638             }
    1639 
    1640             else if(isLineBack){
    1641                 return 'back';
    1642             }
    1643 
    1644             return '';
    1645         },
    1646 
    1647         //添加线至新的形状数组
    1648         addLine = (line, model) => {
    1649             //if(newLines.includes(line) === false && model.containsPoint(line.x, line.y) === false) newLines.push(line);
    1650             if(line.nodes.length === 0) newLines.push(line);
    1651             else if(model.containsPoint(line.x, line.y) === false) newLines.push(line);
    1652             
    1653         },
    1654 
    1655         //处理拥有交点的线
    1656         computeNodes = v => {
    1657             if(v === undefined || v.count === 0) return;
    1658             
    1659             setNodeIndex(v.lines, v.indexed, v.loopType);
    1660         
    1661             //添加交点
    1662             const node = v.line.nodes[v.line.nodeIndex];
    1663             if(newLines.includes(node.point) === false) newLines.push(node.point);
    1664             else return;
    1665 
    1666             var lines = v.lines === linesA ? linesB : linesA, 
    1667             line = lines === linesA ? node.lineA : node.lineB, 
    1668             index = lines.indexOf(line);
    1669 
    1670             setNodeIndex(lines, index);
    1671         
    1672             //选择交点状态
    1673             var nodeState = line.nodeState !== undefined ? line.nodeState : v.line.nodeState;
    1674             if((v.count <= 2 && line.nodes.length > 2) || (v.count > 2 && line.nodes.length <= 2)){
    1675                 if(line.nodeState === 'start'){
    1676                     const backLine = v.loopType === 'next' ? v.lines[v.indexed === 0 ? v.lines.length-1 : v.indexed-1] : v.lines[v.indexed === v.lines.length-1 ? 0 : v.indexed+1];
    1677                     
    1678                     if(newLines.includes(backLine) && backLine.nodes.length === 0){
    1679                         nodeState = 'run';
    1680                     }
    1681 
    1682                 }
    1683                 else if(line.nodeState === 'end'){
    1684                     const nextLine = v.loopType === 'next' ? v.lines[v.indexed === v.lines.length-1 ? 0 : v.indexed+1] : v.lines[v.indexed === 0 ? v.lines.length-1 : v.indexed-1];
    1685                     const model = v.lines === linesA ? polygon : this;
    1686                     if(model.containsPoint(nextLine.x, nextLine.y) === false && nextLine.nodes.length === 0){
    1687                         nodeState = 'run';
    1688                     }
    1689                     
    1690                 }
    1691             }
    1692 
    1693             switch(nodeState){
    1694 
    1695                 //不跳线
    1696                 case 'run': 
    1697                     if(v.loopType === 'back') addLine(v.line, v.lines === linesA ? polygon : this);
    1698                     return computeNodes(loop(v.lines, v.indexed, v.loopType));
    1699 
    1700                 //跳线
    1701                 case 'start': 
    1702                 case 'end': 
    1703                     const loopType = getLoopType(lines, index, node.point);
    1704                     if(loopType !== ''){
    1705                         if(lines === linesA) _loopTypeA = loopType;
    1706                         else _loopTypeB = loopType;
    1707                         if(loopType === 'back') addLine(line, v.lines === linesA ? this : polygon);
    1708                         return computeNodes(loop(lines, index, loopType));
    1709                     }
    1710                     break;
    1711 
    1712             }
    1713 
    1714         }
    1715         
    1716         //获取介入点
    1717         var startLine = null;
    1718         for(let k = 0, len = nodes.length, node; k < len; k++){
    1719             node = nodes[k];
    1720             if(node.lineA.nodes.length !== 0){
    1721                 startLine = node.lineA;
    1722                 if(node.lineA.nodes.length === 1 && node.lineB.nodes.length > 1){
    1723                     startLine = node.lineB.nodes[0].lineB;
    1724                     result_loop.lines = linesB;
    1725                     result_loop.loopType = _loopTypeB = 'next';
    1726                 }
    1727                 else{
    1728                     result_loop.lines = linesA;
    1729                     result_loop.loopType = _loopTypeA = 'next';
    1730                 }
    1731                 result_loop.line = startLine;
    1732                 result_loop.count = startLine.nodes.length;
    1733                 result_loop.indexed = result_loop.lines.indexOf(startLine);
    1734                 break;
    1735             }
    1736         }
    1737 
    1738         if(startLine === null){
    1739             console.warn('Polygon: 找不到介入点, 终止了合并');
    1740             return newLines;
    1741         }
    1742 
    1743         computeNodes(result_loop);
    1744     
    1745         return newLines;
    1746     }
    1747 
    1748 }
    1749 
    1750 
    1751 
    1752 
    1753 /* ShapeRect (一般2d矩形的原点在左上, 此矩形类的原点在中间)
    1754 parameter: 
    1755     width = 0, height = 0
    1756 
    1757 attribute: 
    1758     width, height: Number;     //矩形的宽高
    1759     position: Point;        //位置(是旋转和缩放的中心点)
    1760     rotation: Number;        //旋转(绕 Z 轴旋转的弧度)
    1761     scale: Point;            //缩放
    1762 
    1763 method:
    1764     setFromBox(box): this;                //Box 转为 ShapeRect
    1765     compute(): undefined;                //计算出矩形
    1766     applyCanvas(context): undefined;    //矩形应用到画布的上下文中
    1767 
    1768 demo:
    1769     const canvasRect = new ShapeRect(100, 150); console.log(canvasRect);
    1770 
    1771     const canvas = document.createElement("canvas");
    1772     canvas.width = WORLD.width;
    1773     canvas.height = WORLD.height;
    1774     canvas.style = `
    1775         position: absolute;
    1776         z-index: 9999;
    1777         background: rgb(127,127,127);
    1778     `;
    1779     document.body.appendChild(canvas);
    1780 
    1781     canvasRect.position.set(300, 300);
    1782     canvasRect.rotation = UTILS.toAngle(45);
    1783     canvasRect.scale.set(1.5, 1.5);
    1784     canvasRect.compute();
    1785 
    1786     const context = canvas.getContext("2d");
    1787     canvasRect.applyCanvas(context);
    1788 
    1789     context.strokeStyle = "red";
    1790     context.stroke();
    1791 */
    1792 class ShapeRect{
    1793 
    1794     #dots = [];
    1795     #size = new Point();
    1796     #min = new Point();
    1797     #max = new Point();
    1798 
    1799     constructor(width = 0, height = 0){
    1800         this.width = width;
    1801         this.height = height;
    1802         this.position = new Point();
    1803         this.rotation = 0;
    1804         this.scale = new Point(1, 1);
    1805     }
    1806 
    1807     setFromBox(box){
    1808         this.width = box.w;
    1809         this.height = box.h;
    1810         this.position.set(box.cx, box.cy);
    1811         return this;
    1812     }
    1813 
    1814     compute(){
    1815         //scale
    1816         this.#size.set(this.width * this.scale.x, this.height * this.scale.y);
    1817         
    1818         //position
    1819         this.#min.set(this.position.x - this.#size.x / 2, this.position.y - this.#size.y / 2);
    1820         this.#max.set(this.#min.x + this.#size.x, this.#min.y + this.#size.y);
    1821         
    1822         //rotation
    1823         const point = UTILS.emptyPoint;
    1824         this.#dots.length = 0;
    1825         
    1826         point.set(this.#min.x, this.#min.y).rotate(this.position, this.rotation);
    1827         this.#dots.push(point.x, point.y);
    1828 
    1829         point.set(this.#max.x, this.#min.y).rotate(this.position, this.rotation);
    1830         this.#dots.push(point.x, point.y);
    1831 
    1832         point.set(this.#max.x, this.#max.y).rotate(this.position, this.rotation);
    1833         this.#dots.push(point.x, point.y);
    1834 
    1835         point.set(this.#min.x, this.#max.y).rotate(this.position, this.rotation);
    1836         this.#dots.push(point.x, point.y);
    1837 
    1838         //缩放旋转后的 min 与 max
    1839         //this.#min.set(Math.min(this.#dots[0], this.#dots[2], this.#dots[4], this.#dots[6]), Math.min(this.#dots[1], this.#dots[3], this.#dots[5], this.#dots[7]));
    1840         //this.#max.set(Math.max(this.#dots[0], this.#dots[2], this.#dots[4], this.#dots[6]), Math.max(this.#dots[1], this.#dots[3], this.#dots[5], this.#dots[7]))
    1841     }
    1842 
    1843     drawImage(img, context){
    1844         UTILS.emptyContext.canvas.width = this.#size.x;
    1845         UTILS.emptyContext.canvas.height = this.#size.y;
    1846         UTILS.emptyContext.drawImage(img, 0, 0, this.#size.x, this.#size.y);
    1847 
    1848         const imageData = UTILS.emptyContext.getImageData(0, 0, this.#size.x, this.#size.y),
    1849         width = imageData.width, height = imageData.height,
    1850         len = width * height, point = UTILS.emptyPoint, center = UTILS.emptyPointA.set(width/2, height/2),
    1851 
    1852         newImageData = UTILS.emptyContext.createImageData(width, height);
    1853         
    1854         for(let k = 0; k < len; k++){
    1855             point.set(k % width, Math.floor(k / width)).rotate(center, this.rotation);
    1856             if(point.x < 0 || point.y < 0 || point.x > newImageData.width-1 || point.y > newImageData.height-1) continue;
    1857 
    1858             point.set((width * Math.round(point.y) + Math.round(point.x)) * 4, k * 4);
    1859             newImageData.data[point.x] = imageData.data[point.y];
    1860             newImageData.data[point.x + 1] = imageData.data[point.y + 1];
    1861             newImageData.data[point.x + 2] = imageData.data[point.y + 2];
    1862             newImageData.data[point.x + 3] = imageData.data[point.y + 3];
    1863         }
    1864 
    1865         context.putImageData(newImageData, 20, 20);
    1866     }
    1867 
    1868     applyCanvas(context){
    1869         context.beginPath();
    1870         context.moveTo(this.#dots[0], this.#dots[1]);
    1871 
    1872         for(let k = 2, len = this.#dots.length; k < len; k += 2){
    1873             context.lineTo(this.#dots[k], this.#dots[k + 1]);
    1874         }
    1875 
    1876         context.closePath();
    1877     }
    1878 
    1879 }
    1880 
    1881 
    1882 
    1883 
    1884 /* RGBColor
    1885 parameter: 
    1886     r, g, b
    1887 
    1888 method:
    1889     set(r, g, b: Number): this;            //rgb: 0 - 255; 第一个参数可以为 css color
    1890     setFormHex(hex: Number): this;         //
    1891     setFormHSV(h, s, v: Number): this;    //h:0-360; s,v:0-100; 颜色, 明度, 暗度
    1892     setFormString(str: String): Number;    //str: 英文|css color; 返回的是透明度 (如果为 rgba 则返回a; 否则总是返回1)
    1893 
    1894     copy(v: RGBColor): this;
    1895     clone(): RGBColor;
    1896 
    1897     getHex(): Number;
    1898     getHexString(): String;
    1899     getHSV(result: Object{h, s, v}): result;    //result: 默认是一个新的Object
    1900     getRGBA(alpha: Number): String;             //alpha: 0 - 1; 默认 1
    1901     getStyle()                                     //.getRGBA()别名
    1902 
    1903     stringToColor(str: String): String; //str 转为 css color; 如果str不是color格式则返回 ""
    1904 
    1905 */
    1906 class RGBColor{
    1907 
    1908     constructor(r = 255, g = 255, b = 255){
    1909         this.r = r;
    1910         this.g = g;
    1911         this.b = b;
    1912 
    1913     }
    1914 
    1915     copy(v){
    1916         this.r = v.r;
    1917         this.g = v.g;
    1918         this.b = v.b;
    1919         return this;
    1920     }
    1921 
    1922     clone(){
    1923         return new this.constructor().copy(this);
    1924     }
    1925 
    1926     set(r, g, b){
    1927         if(typeof r !== "string"){
    1928             this.r = r || 255;
    1929             this.g = g || 255;
    1930             this.b = b || 255;
    1931         }
    1932 
    1933         else this.setFormString(r);
    1934         
    1935         return this;
    1936     }
    1937 
    1938     setFormHex(hex){
    1939         hex = Math.floor( hex );
    1940 
    1941         this.r = hex >> 16 & 255;
    1942         this.g = hex >> 8 & 255;
    1943         this.b = hex & 255;
    1944         return this;
    1945     }
    1946 
    1947     setFormHSV(h, s, v){
    1948         h = h >= 360 ? 0 : h;
    1949         var s=s/100;
    1950         var v=v/100;
    1951         var h1=Math.floor(h/60) % 6;
    1952         var f=h/60-h1;
    1953         var p=v*(1-s);
    1954         var q=v*(1-f*s);
    1955         var t=v*(1-(1-f)*s);
    1956         var r,g,b;
    1957         switch(h1){
    1958             case 0:
    1959                 r=v;
    1960                 g=t;
    1961                 b=p;
    1962                 break;
    1963             case 1:
    1964                 r=q;
    1965                 g=v;
    1966                 b=p;
    1967                 break;
    1968             case 2:
    1969                 r=p;
    1970                 g=v;
    1971                 b=t;
    1972                 break;
    1973             case 3:
    1974                 r=p;
    1975                 g=q;
    1976                 b=v;
    1977                 break;
    1978             case 4:
    1979                 r=t;
    1980                 g=p;
    1981                 b=v;
    1982                 break;
    1983             case 5:
    1984                 r=v;
    1985                 g=p;
    1986                 b=q;
    1987                 break;
    1988         }
    1989 
    1990         this.r = Math.round(r*255);
    1991         this.g = Math.round(g*255);
    1992         this.b = Math.round(b*255);
    1993         return this;
    1994     }
    1995 
    1996     setFormString(color){
    1997         if(typeof color !== "string") return 1;
    1998         var _color = this.stringToColor(color);
    1999         
    2000         if(_color[0] === "#"){
    2001             const len = _color.length;
    2002             if(len === 4){
    2003                 _color = _color.slice(1);
    2004                 this.setFormHex(parseInt("0x"+_color + "" + _color));
    2005             }
    2006             else if(len === 7) this.setFormHex(parseInt("0x"+_color.slice(1)));
    2007             
    2008         }
    2009 
    2010         else if(_color[0] === "r" && _color[1] === "g" && _color[2] === "b"){
    2011             const arr = [];
    2012             for(let k = 0, len = _color.length, v = "", is = false; k < len; k++){
    2013                 
    2014                 if(is === true){
    2015                     if(_color[k] === "," || _color[k] === ")"){
    2016                         arr.push(parseFloat(v));
    2017                         v = "";
    2018                     }
    2019                     else v += _color[k];
    2020                     
    2021                 }
    2022 
    2023                 else if(_color[k] === "(") is = true;
    2024                 
    2025             }
    2026 
    2027             this.set(arr[0], arr[1], arr[2]);
    2028             return arr[3] === undefined ? 1 : arr[3];
    2029         }
    2030         
    2031         return 1;
    2032     }
    2033 
    2034     getHex(){
    2035 
    2036         return Math.max( 0, Math.min( 255, this.r ) ) << 16 ^ Math.max( 0, Math.min( 255, this.g ) ) << 8 ^ Math.max( 0, Math.min( 255, this.b ) ) << 0;
    2037 
    2038     }
    2039 
    2040     getHexString(){
    2041 
    2042         return '#' + ( '000000' + this.getHex().toString( 16 ) ).slice( - 6 );
    2043 
    2044     }
    2045 
    2046     getHSV(result){
    2047         result = result || {}
    2048         var r=this.r/255;
    2049         var g=this.g/255;
    2050         var b=this.b/255;
    2051         var h,s,v;
    2052         var min=Math.min(r,g,b);
    2053         var max=v=Math.max(r,g,b);
    2054         var l=(min+max)/2;
    2055         var difference = max-min;
    2056         
    2057         if(max==min){
    2058             h=0;
    2059         }else{
    2060             switch(max){
    2061                 case r: h=(g-b)/difference+(g < b ? 6 : 0);break;
    2062                 case g: h=2.0+(b-r)/difference;break;
    2063                 case b: h=4.0+(r-g)/difference;break;
    2064             }
    2065             h=Math.round(h*60);
    2066         }
    2067         if(max==0){
    2068             s=0;
    2069         }else{
    2070             s=1-min/max;
    2071         }
    2072         s=Math.round(s*100);
    2073         v=Math.round(v*100);
    2074         result.h = h;
    2075         result.s = s;
    2076         result.v = v;
    2077         return result;
    2078     }
    2079 
    2080     getStyle(){
    2081         return this.getRGBA(1);
    2082     }
    2083 
    2084     getRGBA(alpha){
    2085         alpha = typeof alpha === 'number' ? alpha : 1;
    2086         return 'rgba('+this.r+','+this.g+','+this.b+','+alpha+')';
    2087     }
    2088 
    2089     stringToColor(str){
    2090         var _color = "";
    2091         for(let k = 0, len = str.length; k < len; k++){
    2092             if(str[k] === " ") continue;
    2093             _color += str[k];
    2094         }
    2095         
    2096         if(_color[0] === "#" || (_color[0] === "r" && _color[1] === "g" && _color[2] === "b")) return _color;
    2097 
    2098         return ColorRefTable[_color] || "";
    2099     }
    2100 
    2101 }
    2102 
    2103 Object.defineProperties(RGBColor.prototype, {
    2104 
    2105     isRGBColor: {
    2106         configurable: false,
    2107         enumerable: false,
    2108         writable: false,
    2109         value: true,
    2110     }
    2111 
    2112 });
    2113 
    2114 
    2115 
    2116 
    2117 /* Timer 定时器 
    2118 
    2119 parameter:
    2120     func: Function; //定时器运行时的回调; 默认 null, 如果定义构造器将自动调用一次.restart()方法
    2121     speed: Number; //延迟多少毫秒执行一次 func; 默认 3000;
    2122     step: Integer; //执行多少次: 延迟speed毫秒执行一次 func; 默认 Infinity;
    2123     
    2124 attribute:
    2125     func, speed, step;    //这些属性可以随时更改;
    2126 
    2127     //只读属性
    2128     readyState: String;    //定时器状态; 可能值: '', 'start', 'running', 'done'; ''表示定时器从未启动
    2129     number: Number;        //运行的次数
    2130 
    2131 method:
    2132     start(func, speed): this;    //启动定时器 (如果定时器正在运行则什么都不会做)
    2133     restart(): undefined;        //重启定时器
    2134     stop(): undefined;            //停止定时器
    2135 
    2136 demo:
    2137     //每 3000 毫秒 打印一次 timer.number
    2138     new Timer(timer => {
    2139         console.log(timer.number);
    2140     }, 3000);
    2141 
    2142 */
    2143 class Timer{
    2144 
    2145     #restart = -1;
    2146     #speed = 0;
    2147     #isRun = false;
    2148     #i = 0;
    2149     #readyState = ''; //start|running
    2150 
    2151     get number(){
    2152         return this.#i;
    2153     }
    2154     
    2155     get readyState(){
    2156         return this.#i >= this.step ? 'done' : this.#readyState;
    2157     }
    2158 
    2159     get running(){
    2160         return this.#isRun;
    2161     }
    2162 
    2163     constructor(func = null, speed = 3000, step = Infinity){
    2164         this.func = func;
    2165         this.speed = speed;
    2166         this.step = step;
    2167         //this.onDone = null;
    2168     
    2169         if(typeof this.func === "function") this.restart();
    2170 
    2171     }
    2172 
    2173     start(func, time){
    2174         if(typeof func === 'function') this.func = func;
    2175         if(UTILS.isNumber(time) === true) this.speed = time;
    2176         this.restart();
    2177 
    2178         return this;
    2179     }
    2180 
    2181     restart(){
    2182         if(this.#isRun === false){
    2183             setTimeout(this._loop, this.speed);
    2184             this.#isRun = true;
    2185             this.#restart = -1;
    2186             this.#i = 0;
    2187             this.#readyState = 'start';
    2188             
    2189         }
    2190 
    2191         else{
    2192             this.#restart = Date.now();
    2193             this.#speed = this.speed;
    2194 
    2195         }
    2196 
    2197     }
    2198 
    2199     stop(){
    2200         if(this.#isRun === true){
    2201             this.#restart = -1;
    2202             this.#i = this.step;
    2203         }
    2204 
    2205     }
    2206 
    2207     _loop = () => {
    2208 
    2209         //重启计时器
    2210         if(this.#restart !== -1){
    2211             
    2212             let gone = Date.now() - this.#restart;
    2213             this.#restart = -1;
    2214             
    2215             if(gone >= this.#speed) gone = this.speed;
    2216             else{
    2217                 if(this.#speed === this.speed) gone = this.#speed - gone;
    2218                 else gone = (this.#speed - gone) / this.#speed * this.speed;
    2219             }
    2220             
    2221             setTimeout(this._loop, gone);
    2222             
    2223             this.#i = 1;
    2224             if(this.func !== null) this.func(this);
    2225 
    2226         }
    2227 
    2228         //正在运行
    2229         else if(this.#i < this.step){
    2230 
    2231             setTimeout(this._loop, this.speed);
    2232 
    2233             this.#i++;
    2234             if(this.#readyState !== 'running') this.#readyState = 'running';
    2235             if(this.func !== null) this.func(this);
    2236 
    2237         }
    2238 
    2239         //完成
    2240         else this.#isRun = false;
    2241 
    2242     }
    2243 
    2244 }
    2245 
    2246 
    2247 
    2248 
    2249 /* SeekPath A*寻路
    2250 
    2251 parameter: 
    2252     option: Object{
    2253         angle: Number,         //8 || 16
    2254         timeout: Number,     //单位为毫秒
    2255         size: Number,         //每格的宽高
    2256         lenX, lenY: Number,    //长度
    2257         disables: Array[0||1],
    2258         heights: Array[Number],
    2259         path: Array[], //存放寻路结果 默认创建一个空数组
    2260     }
    2261 
    2262 attribute:
    2263     size: Number;     //每个索引的大小
    2264     lenX: Number;     //最大长度x (设置此属性时, 你需要重新.initMap(heights);)
    2265     lenY: Number;     //最大长度y (设置此属性时, 你需要重新.initMap(heights);)
    2266 
    2267     //此属性已废弃 range: Box;            //本次的搜索范围, 默认: 0,0,lenX,lenY
    2268     angle: Number;         //8四方向 或 16八方向 默认 16
    2269     timeout: Number;     //超时毫秒 默认 500
    2270     mapRange: Box;        //地图box
    2271     //此属性已废弃(run函数不在检测相邻的高) maxHeight: Number;     //相邻可走的最大高 默认 6
    2272 
    2273     //只读属性
    2274     success: Bool;            //只读; 本次搜索是否成功找到终点; (如果为false说明.run()返回的是 距终点最近的路径; 超时也会被判定为false)
    2275     path: Array[x, y, z];    //存放.run()返回的路径
    2276     map: Map;                 //地图的缓存数据
    2277 
    2278 method:
    2279     initMap(heights: Array[Number]): undefiend;     //初始化类时自动调用一次; heights:如果你的场景存在高请定义此参数
    2280     run(x, y, x1, y1: Number): Array[x, y, z];         //参数索引坐标
    2281     getDots(x, y, a, r): Array[ix, iy];             //获取周围的点 x,y, a:8|16, r:存放结果数组
    2282     getLegalPoints(ix, iy, count): Array[x, y, z];    //获取 ix, iy 周围 合法的, 相邻的 count 个点
    2283 
    2284 demo:
    2285     const sp = new SeekPath({
    2286         angle: 16,
    2287         timeout: 500,
    2288         //maxHeight: 6,
    2289         size: 10,
    2290         lenX: 1000,
    2291         lenY: 1000,
    2292     }),
    2293 
    2294     path = sp.run(0, 0, 1000, 1000);
    2295 
    2296     console.log(sp);
    2297 
    2298 */
    2299 class SeekPath{
    2300 
    2301     static _open = []
    2302     static _dots = [] //.run() .getLegalPoints()
    2303     static dots4 = []; //._check()
    2304     static _sort = function (a, b){return a["f"] - b["f"];}
    2305 
    2306     #map = null;
    2307     #path = null;
    2308     #success = true;
    2309     #halfX = 50;
    2310     #halfY = 50;
    2311 
    2312     #size = 10;
    2313     #lenX = 10;
    2314     #lenY = 10;
    2315 
    2316     constructor(option = {}){
    2317         this.angle = (option.angle === 8 || option.angle === 16) ? option.angle : 16; //8四方向 或 16八方向
    2318         this.timeout = option.timeout || 500; //超时毫秒
    2319         //this.maxHeight = option.maxHeight || 6;
    2320         this.mapRange = new Box();
    2321         this.size = option.size || 10;
    2322         this.lenX = option.lenX || 10;
    2323         this.lenY = option.lenY || 10;
    2324         this.#path = Array.isArray(option.path) ? option.path : [];
    2325         this.initMap(option.disable, option.height);
    2326         option = undefined
    2327     }
    2328 
    2329     //this.#map[x][y] = Object{height: 此点的高,默认0, is: 此点是否可走,默认1(disable)};
    2330     get map(){
    2331         return this.#map;
    2332     }
    2333 
    2334     //this.#path = Array[x,y,z]
    2335     get path(){
    2336         return this.#path;
    2337     }
    2338 
    2339     get success(){
    2340         return this.#success;
    2341     }
    2342 
    2343     get size(){
    2344         return this.#size;
    2345     }
    2346 
    2347     set size(v){
    2348         this.#size = v;
    2349         v = v / 2;
    2350         this.#halfX = v * this.#lenX;
    2351         this.#halfY = v * this.#lenY;
    2352     }
    2353 
    2354     get lenX(){
    2355         return this.#lenX;
    2356     }
    2357 
    2358     set lenX(v){
    2359         this.#lenX = v;
    2360         v = this.#size / 2;
    2361         this.#halfX = v * this.#lenX;
    2362         this.#halfY = v * this.#lenY;
    2363         
    2364     }
    2365 
    2366     get lenY(){
    2367         return this.#lenY;
    2368     }
    2369 
    2370     set lenY(v){
    2371         this.#lenY = v;
    2372         v = this.#size / 2;
    2373         this.#halfX = v * this.#lenX;
    2374         this.#halfY = v * this.#lenY;
    2375         
    2376     }
    2377 
    2378     toScene(n, v){ //n = "x|y"
    2379         //n = n === "y" ? "lenY" : "lenX";
    2380         if(n === "y" || n === "z") return v * this.#size - this.#halfY;
    2381         return v * this.#size - this.#halfX;
    2382     
    2383     }
    2384     
    2385     toIndex(n, v){
    2386         //n = n === "y" ? "lenY" : "lenX";
    2387         if(n === "y" || n === "z") return Math.round((this.#halfY + v) / this.#size);
    2388         return Math.round((this.#halfX + v) / this.#size);
    2389 
    2390     }
    2391 
    2392     initMap(disable, height){
    2393         
    2394         disable = Array.isArray(disable) === true ? disable : null;
    2395         height = Array.isArray(height) === true ? height : null;
    2396         
    2397         const lenX = this.lenX, lenY = this.lenY;
    2398         var getHeight = (ix, iy) => {
    2399             if(height === null) return 0;
    2400             ix = height[ix * lenY + iy];
    2401             if(ix === undefined) return 0;
    2402             return ix;
    2403         },
    2404         getDisable = (ix, iy) => {
    2405             if(disable === null) return 1;
    2406             ix = disable[ix * lenY + iy];
    2407             if(ix === undefined) return 0;
    2408             return ix;
    2409         },
    2410 
    2411         map = []//new Map();
    2412 
    2413         for(let x = 0, y, m; x < lenX; x++){
    2414             m = []//new Map();
    2415             for(y = 0; y < lenY; y++) m[y] = {x:x, y:y, height:getHeight(x, y), is:getDisable(x, y),  g:0, h:0, f:0, p:null, id:""}//m.set(y, {x:x, y:y, height:getHeight(x, y),   g:0, h:0, f:0, p:null, id:""});
    2416             map[x] = m;//map.set(x, m);
    2417         }
    2418         
    2419         this.#map = map;
    2420         this._id = -1;
    2421         this._updateID();
    2422         this.mapRange.set(0, 0, this.#lenX-1, this.#lenY-1);
    2423 
    2424         map = disable = height = getHeight = undefined;
    2425 
    2426     }
    2427 
    2428     getLegalPoints(ix, iy, count, result = []){
    2429         const _dots = SeekPath._dots;
    2430         result.length = 0;
    2431         result[0] = this.#map[ix][iy];
    2432         count += 1;
    2433         
    2434         while(result.length < count){
    2435             for(let k = 0, i, n, d, len = result.length; k < len; k++){
    2436                 n = result[k];
    2437                 this.getDots(n.x, n.y, this.angle, _dots);
    2438                 for(i = 0; i < this.angle; i += 2){
    2439                     d = this.#map[_dots[i]][_dots[i+1]];
    2440                     if(d.is === 1 && this.mapRange.containsPoint(d.x, d.y) && !result.includes(d)){
    2441                         if(Math.abs(n.x - d.x) + Math.abs(n.y - d.y) === 2 && this._check(n, d)){
    2442                             result.push(d);
    2443                         }
    2444                     }
    2445                 }
    2446             }
    2447         }
    2448     
    2449         result.splice(0, 1);
    2450         return result;
    2451     }
    2452 
    2453     getLinePoints(now, next, count, result = []){
    2454         if(count === 1) return result[0] = next;
    2455         if(count % 2 === 0) count += 1;
    2456 
    2457         const len = Math.floor(count / 2), 
    2458         nowPoint = UTILS.emptyPoint, 
    2459         angle90 = UTILS.toAngle(90);
    2460 
    2461         var i, ix, iy, n, nn = next;
    2462 
    2463         nowPoint.set(now.x, now.y).rotate(next, angle90); //now 以 next 为原点顺时针旋转 90 度
    2464         var disX = nowPoint.x - next.x, 
    2465         disY = nowPoint.y - next.y;
    2466         
    2467         for(i = 0; i < len; i++){
    2468             ix = disX + disX * i + next.x;
    2469             iy = disY + disY * i + next.y;
    2470 
    2471             n = this.#map[ix][iy];
    2472             if(n.is === 1) nn = n;
    2473             result[len-1-i] = nn;
    2474         }
    2475 
    2476         result[len] = next;
    2477         nn = next
    2478 
    2479         nowPoint.set(now.x, now.y).rotate(next, -angle90);
    2480         disX = nowPoint.x - next.x; 
    2481         disY = nowPoint.y - next.y;
    2482 
    2483         for(i = 0; i < len; i++){
    2484             ix = disX + disX * i + next.x;
    2485             iy = disY + disY * i + next.y;
    2486 
    2487             n = this.#map[ix][iy];
    2488             if(n.is === 1) nn = n;
    2489             result[len+1+i] = nn;
    2490         }
    2491 
    2492         return result;
    2493     }
    2494 
    2495     getDots(x, y, a, r = []){ //获取周围的点 x,y, a:8|16, r:存放结果数组
    2496         r.length = 0;
    2497         const x_1 = x-1, x1 = x+1, y_1 = y-1, y1 = y+1;
    2498         if(a === 16) r.push(x_1, y_1, x, y_1, x1, y_1, x_1, y, x1, y, x_1, y1, x, y1, x1, y1);
    2499         else r.push(x, y_1, x, y1, x_1, y, x1, y);
    2500     }
    2501 
    2502     _updateID(){ //更新标记
    2503         this._id++;
    2504         this._openID = "o_"+this._id;
    2505         this._closeID = "c_"+this._id;
    2506     }
    2507 
    2508     _check(dotA, dotB){ //检测 a 是否能到 b
    2509         //获取 dotB 周围的4个点 并 遍历这4个点
    2510         this.getDots(dotB.x, dotB.y, 8, SeekPath.dots4);
    2511         for(let k = 0, x, y; k < 8; k += 2){
    2512             x = SeekPath.dots4[k]; 
    2513             y = SeekPath.dots4[k+1];
    2514             if(this.mapRange.containsPoint(x, y) === false) continue;
    2515 
    2516             //找出 dotA 与 dotB 相交的两个点:
    2517             if(Math.abs(dotA.x - x) + Math.abs(dotA.y - y) === 1){
    2518                 //如果其中一个交点是不可走的则 dotA 到 dotB 不可走, 既返回 false
    2519                 if(this.#map[x][y].is === 0) return false;
    2520             }
    2521 
    2522         }
    2523 
    2524         return true;
    2525     }
    2526 
    2527     run(x, y, x1, y1, path = this.#path){
    2528         path.length = 0;
    2529         if(this.#map === null || this.mapRange.containsPoint(x, y) === false) return path;
    2530         
    2531         var _n = this.#map[x][y];
    2532         if(_n.is === 0) return path;
    2533 
    2534         const _sort = SeekPath._sort,
    2535         _open = SeekPath._open,
    2536         _dots = SeekPath._dots, 
    2537         time = Date.now();
    2538 
    2539         //var isDot = true, 
    2540         var suc = _n, k, mhd, g, h, f, _d;
    2541 
    2542         _n.g = 0;
    2543         _n.h = _n.h = Math.abs(x1 - x) * 10 + Math.abs(y1 - y) * 10; 
    2544         _n.f = _n.h;
    2545         _n.p = null;
    2546         this._updateID();
    2547         _n.id = this._openID;
    2548         _open.push(_n);
    2549         
    2550         while(_open.length !== 0){
    2551             if(Date.now() - time > this.timeout) break;
    2552 
    2553             _open.sort(_sort);
    2554             _n = _open.shift();
    2555             if(_n.x === x1 && _n.y === y1){
    2556                 suc = _n;
    2557                 break;
    2558             }
    2559             
    2560             if(suc.h > _n.h) suc = _n;
    2561             _n.id = this._closeID;
    2562             this.getDots(_n.x, _n.y, this.angle, _dots);
    2563             
    2564             for(k = 0; k < this.angle; k += 2){
    2565                 
    2566                 _d = this.#map[_dots[k]][_dots[k+1]];
    2567                 if(_d.id === this._closeID || _d.is === 0 || this.mapRange.containsPoint(_d.x, _d.y) === false) continue;
    2568 
    2569                 mhd = Math["abs"](_n["x"] - _d.x) + Math["abs"](_n["y"] - _d.y);
    2570                 g = _n["g"] + (mhd === 1 ? 10 : 14);
    2571                 h = Math["abs"](x1 - _d.x) * 10 + Math["abs"](y1 - _d.y) * 10;
    2572                 f = g + h;
    2573             
    2574                 if(_d.id !== this._openID){
    2575                     //如果是斜角和8方向:
    2576                     if(mhd !== 1 && this.angle === 16){
    2577                         if(this._check(_n, _d)){
    2578                             _d.g = g;
    2579                             _d.h = h;
    2580                             _d.f = f;
    2581                             _d.p = _n;
    2582                             _d.id = this._openID;
    2583                             _open.push(_d);
    2584                         }
    2585                     }else{
    2586                         _d.g = g;
    2587                         _d.h = h;
    2588                         _d.f = f;
    2589                         _d.p = _n;
    2590                         _d.id = this._openID;
    2591                         _open.push(_d);
    2592                     }
    2593                 }
    2594 
    2595                 else if(g < _d.g){
    2596                     _d.g = g;
    2597                     _d.f = g + _d.h;
    2598                     _d.p = _n;
    2599                 }
    2600     
    2601             }
    2602         }
    2603 
    2604         this.#success = suc === _n;
    2605 
    2606         while(suc !== null){
    2607             path.unshift(suc);
    2608             //path.unshift(this.toScene("x", suc["x"]), suc["height"], this.toScene("y", suc["y"]));
    2609             suc = suc["p"];
    2610         }
    2611 
    2612         _open.length = _dots.length = 0;
    2613         
    2614         return path;
    2615     }
    2616 
    2617 }
    2618 
    2619 
    2620 
    2621 
    2622 /* RunningList
    2623     如果触发过程中删除(回调函数中删除正在遍历的数组), 不仅 len 没有变(遍历前定义的len没有变, 真实的len随之减少), 而且还会漏掉一个key;
    2624 
    2625 */
    2626 class RunningList{
    2627 
    2628     /* static getProxy(runName){
    2629 
    2630         return new Proxy(new RunningList(runName), {
    2631 
    2632             get(tar, key){
    2633                 
    2634             },
    2635 
    2636             set(tar, key, val){
    2637                 
    2638             }
    2639             
    2640         });
    2641 
    2642     } */
    2643 
    2644     constructor(runName = 'update'){
    2645         this._running = false;
    2646         this._list = [];
    2647         this._delList = [];
    2648         this._runName = runName;
    2649     }
    2650 
    2651     get length(){
    2652 
    2653         return this._list.length;
    2654 
    2655     }
    2656 
    2657     add(v){
    2658 
    2659         if(!this._list.includes(v)) this._list.push(v);
    2660 
    2661     }
    2662 
    2663     clear(){
    2664         this._list.length = 0;
    2665         this._delList.length = 0;
    2666     }
    2667 
    2668     push(...v){
    2669 
    2670         v.forEach(_v => this._list.push(_v));
    2671 
    2672     }
    2673 
    2674     splice(v){
    2675         if(this._running === true){
    2676             if(!this._delList.includes(v)) this._delList.push(v);
    2677         }
    2678 
    2679         else{
    2680             const i = this._list.indexOf(v);
    2681             if(i !== -1) this._list.splice(i, 1);
    2682         }
    2683 
    2684     }
    2685 
    2686     update(){
    2687 
    2688         var k, len = this._list.length;
    2689 
    2690         this._running = true;
    2691         if(this._runName !== ''){
    2692             for(k = 0; k < len; k++) this._list[k][this._runName]();
    2693         }else{
    2694             for(k = 0; k < len; k++) this._list[k]();
    2695         }
    2696         this._running = false;
    2697 
    2698         var i;
    2699         len = this._delList.length;
    2700         for(k = 0; k < len; k++){
    2701             //this.splice(this._delList[k]);
    2702             i = this._list.indexOf(this._delList[k]);
    2703             if(i !== -1) this._list.splice(i, 1);
    2704         }
    2705         this._delList.length = 0;
    2706         
    2707     }
    2708 
    2709 }
    2710 
    2711 
    2712 
    2713 
    2714 /* TweenValue (从 原点 以规定的时间到达  终点)
    2715 
    2716 parameter: origin, end, time, onUpdate, onEnd;
    2717 
    2718 attribute:
    2719     origin: Object; //原点(起点)
    2720     end: Object; //终点
    2721     time: Number; //origin 到 end 花费的时间
    2722     onUpdate: Function; //更新回调; 一个回调参数 origin; 默认null;
    2723     onEnd: Function; //结束回调; 没有回调参数; 默认null; (如果返回的是"restart"将不从队列删除, 你可以在onEnd中更新.end不间断的继续补间)
    2724 
    2725 method:
    2726     reset(origin, end: Object): undefined; //更换 .origin, .end; 它会清除其它对象的缓存属性
    2727     reverse(): undefined; //this.end 复制 this.origin 的原始值
    2728     update(): undefined; //Tween 通过此方法统一更新 TweenValue
    2729 
    2730 demo: 
    2731     //init Tween:
    2732     const tween = new Tween(),
    2733     animate = function (){
    2734         requestAnimationFrame(animate);
    2735         tween.update();
    2736     }
    2737 
    2738     //init TweenValue:
    2739     const v1 = new Tween.Value({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v));
    2740     
    2741     animate();
    2742     tween.start(v1);
    2743 
    2744 */
    2745 class TweenValue{
    2746 
    2747     constructor(origin = {}, end = {}, time = 500, onUpdate = null, onEnd = null, onStart = null){
    2748         this.origin = origin;
    2749         this.end = end;
    2750         this.time = time;
    2751 
    2752         this.onUpdate = onUpdate;
    2753         this.onEnd = onEnd;
    2754         this.onStart = onStart;
    2755         
    2756         //以下属性不能直接设置
    2757         this._r = null;
    2758         this._t = 0;
    2759         this._v = Object.create(null);
    2760 
    2761     }
    2762 
    2763     _start(){
    2764         var v = "";
    2765         for(v in this.origin) this._v[v] = this.origin[v];
    2766 
    2767         this._t = Date.now();
    2768         //this.update();
    2769 
    2770     }
    2771 
    2772     reset(origin, end){
    2773         this.origin = origin;
    2774         this.end = end;
    2775         this._v = Object.create(null);
    2776 
    2777     }
    2778 
    2779     reverse(){
    2780         var n = "";
    2781         for(n in this.origin) this.end[n] = this._v[n];
    2782 
    2783     }
    2784 
    2785     update(){
    2786 
    2787         if(this["_r"] !== null){
    2788 
    2789             var ted = Date["now"]() - this["_t"];
    2790 
    2791             if(ted >= this["time"]){
    2792 
    2793                 for(ted in this["origin"]) this["origin"][ted] = this["end"][ted];
    2794 
    2795                 if(this["onEnd"] !== null){
    2796 
    2797                     if(this["onEnd"](this) === "restart"){
    2798                         if(this["onUpdate"] !== null) this["onUpdate"](this["origin"]);
    2799                         this["_start"]();
    2800                     }
    2801 
    2802                     else this["_r"]["stop"](this);
    2803                     
    2804                 }
    2805 
    2806                 else this["_r"]["stop"](this);
    2807 
    2808             }
    2809 
    2810             else{
    2811                 ted = ted / this["time"];
    2812                 let n = "";
    2813                 for(n in this["origin"]) this["origin"][n] = ted * (this["end"][n] - this["_v"][n]) + this["_v"][n];
    2814                 if(this["onUpdate"] !== null) this["onUpdate"](this["origin"]);
    2815             }
    2816 
    2817         }
    2818 
    2819     }
    2820 
    2821 }
    2822 
    2823 Object.defineProperties(TweenValue.prototype, {
    2824 
    2825     isTweenValue: {
    2826         configurable: false,
    2827         enumerable: false,
    2828         writable: false,
    2829         value: true,
    2830     }
    2831 
    2832 });
    2833 
    2834 
    2835 
    2836 
    2837 /* TweenAlone (相对于 TweenValue 此类可以独立补间, 不需要 Tween)
    2838 
    2839 demo:
    2840     const v1 = new TweenAlone({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v)),
    2841     animate = function (){
    2842         requestAnimationFrame(animate);
    2843         v1.update();
    2844     }
    2845 
    2846     animate();
    2847     v1.start();
    2848 
    2849 */
    2850 class TweenAlone extends TweenValue{
    2851 
    2852     constructor(origin, end, time, onUpdate, onEnd, onStart){
    2853         super(origin, end, time, onUpdate, onEnd, onStart);
    2854         
    2855     }
    2856 
    2857     start(){
    2858         if(this.onStart !== null) this.onStart();
    2859         this._r = this;
    2860         this._start();
    2861 
    2862     }
    2863 
    2864     stop(){
    2865         this._r = null;
    2866         
    2867     }
    2868 
    2869 }
    2870 
    2871 
    2872 
    2873 
    2874 
    2875 /* Tween 动画补间 (TweenValue 的root, 可以管理多个TweenValue)
    2876 
    2877 parameter:
    2878 attribute:
    2879 method:
    2880     start(value: TweenValue): undefined;
    2881     stop(value: TweenValue): undefined;
    2882 
    2883 static:
    2884     Value: TweenValue;
    2885 
    2886 demo:
    2887     //init Tween:
    2888     const tween = new Tween(),
    2889     animate = function (){
    2890         requestAnimationFrame(animate);
    2891         tween.update();
    2892     }
    2893 
    2894     //init TweenValue:
    2895     const v2 = new Tween.Value({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v), v => {
    2896         v2.reverse(); //v2.end 复制起始值
    2897         return "restart"; //返回"restart"表示不删除队列, 需要继续补间
    2898     });
    2899     
    2900     animate();
    2901     tween.start(v2);
    2902 
    2903 */
    2904 class Tween extends RunningList{
    2905 
    2906     static Value = TweenValue;
    2907 
    2908     constructor(){
    2909         super();
    2910 
    2911     }
    2912 
    2913     start(value){
    2914         if(value.onStart !== null) value.onStart();
    2915         if(value._r === null) this.push(value);
    2916         value._r = this;
    2917         value._start(this);
    2918 
    2919     }
    2920 
    2921     stop(value){
    2922         if(value._r !== null) this.splice(value);
    2923         value._r = null;
    2924         
    2925     }
    2926 
    2927 }
    2928 
    2929 
    2930 
    2931 
    2932 /* TweenTarget 朝着轴插值(有效的跟踪动态目标, 注意此类需要配合 RunningList 类使用, 因为此类在任何情况下都没有阻止你调用.update()方法)
    2933 parameter:    
    2934     v1 = {x: 0}, 
    2935     v2 = {x: 100}, 
    2936     distance = 1,        //每次移动的距离
    2937     onUpdate = null,    //
    2938     onEnd = null
    2939 
    2940 attribute:
    2941     v1: Object;             //起点
    2942     v2: Object;             //终点
    2943     onUpdate: Function;        //
    2944     onEnd: Function;         //
    2945 
    2946 method:
    2947     update(): undefined;                        //一般在动画循环里执行此方法
    2948     updateAxis(): undefined;                     //更新v1至v2的方向轴 (初始化时构造器自动调用一次)
    2949     setDistance(distance: Number): undefined;     //设置每次移动的距离 (初始化时构造器自动调用一次)
    2950 
    2951 demo:
    2952     const ttc = new TweenTarget({x:0, y:0}, {x:100, y:100}, 10),
    2953 
    2954     //计时器模拟动画循环函数, 每秒执行一次.update()
    2955     timer = new Timer(() => {
    2956         ttc.update();
    2957         console.log('update: ', ttc.v1);
    2958 
    2959     }, 1000, Infinity);
    2960 
    2961     ttc.onEnd = v => {
    2962         timer.stop();
    2963         console.log('end: ', v);
    2964     }
    2965 
    2966     timer.start();
    2967     console.log(ttc);
    2968 
    2969 */
    2970 class TweenTarget{
    2971 
    2972     #distance = 1;
    2973     #distancePow2 = 1;
    2974     #axis = {};
    2975 
    2976     constructor(v1 = {x: 0}, v2 = {x: 100}, distance, onUpdate = null, onEnd = null){
    2977         this.v1 = v1;
    2978         this.v2 = v2;
    2979         this.onUpdate = onUpdate;
    2980         this.onEnd = onEnd;
    2981     
    2982         this.setDistance(distance);
    2983         this.updateAxis();
    2984     }
    2985 
    2986     setDistance(v = 1){
    2987         this.#distance = v;
    2988         this.#distancePow2 = Math.pow(v, 2);
    2989     }
    2990 
    2991     updateAxis(){
    2992         var n, v, len = 0;
    2993 
    2994         for(n in this.v1){
    2995             v = this.v2[n] - this.v1[n];
    2996             len += v * v;
    2997             this.#axis[n] = v;
    2998 
    2999         }
    3000 
    3001         len = Math.sqrt(len);
    3002 
    3003         if(len !== 0){
    3004             
    3005             for(n in this.v1) this.#axis[n] *= 1 / len;
    3006 
    3007         }
    3008     }
    3009 
    3010     update(){
    3011         var n, len = 0;
    3012 
    3013         for(n in this.v1) len += Math.pow(this.v1[n] - this.v2[n], 2);
    3014 
    3015         if(len > this.#distancePow2){
    3016 
    3017             for(n in this.v1) this.v1[n] += this.#axis[n] * this.#distance;
    3018             if(this.onUpdate !== null) this.onUpdate(this.v1);
    3019 
    3020         }
    3021 
    3022         else{
    3023 
    3024             for(n in this.v1) this.v1[n] = this.v2[n];
    3025             if(this.onEnd !== null) this.onEnd(this.v1);
    3026             
    3027         }
    3028     }
    3029 
    3030 }
    3031 
    3032 
    3033 
    3034 
    3035 /* EventDispatcher 自定义事件管理器
    3036 parameter: 
    3037 attribute: 
    3038 
    3039 method:
    3040     clearEvents(eventName): undefined;             //清除eventName列表, 如果 eventName 未定义清除所有事件
    3041     customEvents(eventName, eventParam): this;    //创建自定义事件 eventParam 可选 默认{}
    3042     getParam(eventName): eventParam;
    3043     trigger(eventName, callback): undefined;    //触发 (callback: 可选)
    3044     register(eventName, callback): undefined;    //
    3045     deleteEvent(eventName, callback): undefined; //
    3046 
    3047 demo:
    3048     const eventDispatcher = new EventDispatcher();
    3049     eventDispatcher.customEvents("test", {name: "test"});
    3050 
    3051     eventDispatcher.register("test", eventParam => {
    3052         console.log(eventParam) //Object{name: "test"}
    3053     });
    3054 
    3055     eventDispatcher.trigger("test");
    3056 
    3057 */
    3058 class EventDispatcher{
    3059     
    3060     constructor(){
    3061         this._eventsList = {};
    3062         this.__eventsList = [];
    3063         this.__trigger = "";
    3064 
    3065     }
    3066 
    3067     clearEvents(eventName){ 
    3068 
    3069         //if(this.__trigger === eventName) return console.warn("EventDispatcher: 清除事件失败");
    3070         if(this._eventsList[eventName] !== undefined) this._eventsList[eventName].func = []
    3071 
    3072         else this._eventsList = {}
    3073 
    3074     }
    3075     
    3076     customEvents(eventName, eventParam = {}){ 
    3077         //if(typeof eventName !== "string") return console.warn("EventDispatcher: 注册自定义事件失败");
    3078         if(this._eventsList[eventName] !== undefined) return console.warn("EventDispatcher: "+eventName+" 已存在");
    3079         //eventParam = eventParam || {}
    3080         //if(eventParam.type === undefined) eventParam.type = eventName;
    3081         this._eventsList[eventName] = {func: [], param: eventParam}
    3082         return this;
    3083     }
    3084 
    3085     getParam(eventName){
    3086         return this._eventsList[eventName]["param"];
    3087     }
    3088     
    3089     trigger(eventName, callback){
    3090         //if(this._eventsList[eventName] === undefined) return;
    3091         
    3092         const obj = this._eventsList[eventName];
    3093         var k, len = obj.func.length;
    3094 
    3095         if(len !== 0){
    3096             if(typeof callback === "function") callback(obj["param"]); //更新参数(eventParam)
    3097 
    3098             //触发过程(如果触发过程中删除事件, 不仅 len 没有变, 而且还会漏掉一个key, 所以在触发过程中删除的事件要特殊处理)
    3099             this.__trigger = eventName;
    3100             for(k = 0; k < len; k++) obj["func"][k](obj["param"]);
    3101             this.__trigger = "";
    3102             //触发过程结束
    3103             
    3104             //删除在触发过程中要删除的事件
    3105             len = this.__eventsList.length;
    3106             for(k = 0; k < len; k++) this.deleteEvent(eventName, this.__eventsList[k]);
    3107             this.__eventsList.length = 0;
    3108     
    3109         }
    3110 
    3111     }
    3112     
    3113     register(eventName, callback){
    3114         if(this._eventsList[eventName] === undefined) return console.warn("EventDispatcher: "+eventName+" 不存在");
    3115         const obj = this._eventsList[eventName];
    3116         //if(obj.func.includes(callback) === false) obj.func.push(callback);
    3117         //else console.warn("EventDispatcher: 回调函数重复");
    3118         obj.func.push(callback);
    3119     }
    3120     
    3121     deleteEvent(eventName, callback){
    3122         if(this._eventsList[eventName] === undefined) return console.warn("EventDispatcher: "+eventName+" 不存在");
    3123         
    3124         if(this.__trigger === eventName) this.__eventsList.push(callback);
    3125         else{
    3126             const obj = this._eventsList[eventName], i = obj.func.indexOf(callback);
    3127             if(i !== -1) obj.func.splice(i, 1);
    3128         }
    3129         
    3130     }
    3131 
    3132 }
    3133 
    3134 
    3135 
    3136 
    3137 export {
    3138     UTILS, 
    3139     ColorRefTable, 
    3140     AnimateLoop,
    3141     Ajax, 
    3142     IndexedDB, 
    3143     TreeStruct, 
    3144     Point, 
    3145     Line, 
    3146     Box, 
    3147     Circle, 
    3148     Polygon, 
    3149     RGBColor, 
    3150     Timer, 
    3151     SeekPath, 
    3152     RunningList, 
    3153     TweenValue, 
    3154     TweenAlone, 
    3155     Tween, 
    3156     TweenTarget, 
    3157     EventDispatcher, 
    3158     Rotate,
    3159     ShapeRect,
    3160 }
    Utils.js
       1 "use strict";
       2 
       3 import {
       4     UTILS, 
       5     Box, 
       6     Rotate,
       7     EventDispatcher,
       8     Point,
       9     AnimateLoop,
      10 } from './Utils.js';
      11 
      12 
      13 /* CanvasImageEvent (CanvasImageRender 内部用此类管理 dom 事件)
      14 ca 必须的属性: visible, box, rotate, index
      15 ca 必须的方法: 没有;
      16 
      17 parameter:
      18     domElement: CanvasImageRender.domElement; //必须
      19     box: CanvasImageRender.box; //必须
      20     ||或者第一个参数为 CanvasImageRender
      21 
      22     与.initEvent(domElement, box)参数一样
      23 
      24 attribute:
      25     domElement
      26     domElementPos
      27     box: Box;
      28 
      29 method:
      30     add(ca: CanvasImage, eventName: String, callback: Function): ca; //ca添加事件
      31     remove(ca: CanvasImage, eventName: String, callback: Function): ca; //ca删除事件
      32         eventName: 可能的值为 CanvasImageEvent.canvasEventsList 的属性名
      33         callback: 参数 event, ca
      34     
      35     clear(ca: CanvasImage, eventName: String): ca; //ca 必须; eventName 可选, 如果未定义则清空ca的所有事件;
      36     disposeEvent(eventName): this; //.initEvent(domElement, box)调用一次此方法
      37     initEvent(domElement, box): this; //每次更换 domElement, box 后应调用此方法; (CanvasAnimateEvent初始化时自动调用一次)
      38     setScale(x, y: Number): undefiend; //
      39 
      40 eventName:
      41     (如果注册了 "out"|"over" 事件, 在弃用时(CanvasImageRender 不在使用): 
      42     必需要调用.disposeEvent(eventName)方法清理注册的dom事件, 
      43     因为这两个事件用的是 pointermove 而不是 onpointermove);
      44 
      45     down    //鼠标左右键按下
      46     move    //鼠标左右键移动
      47     up        //鼠标左右键抬起
      48 
      49     click    //鼠标左键
      50     wheel    //鼠标滚动轮
      51     out        //移出
      52     over    //移入
      53 
      54 demo:
      55     const car = new CanvasImageRender({ 100, height: 100}),
      56     cae = new CanvasImageEvent(car),
      57     ca = car.add(new CanvasImage(image));
      58 
      59     //ca添加点击事件
      60     cae.add(ca, 'click', (event, target) => console.log(event, target));
      61 
      62     car.render();
      63 
      64 */
      65 class CanvasImageEvent{
      66 
      67     static bind(obj, is){
      68         /* const _eventList = {}
      69         Object.defineProperty(obj, "_eventList", {
      70             get(){return _eventList;}
      71         }); */
      72         obj._eventList = {};
      73         if(is === true){
      74             let k, evns = CanvasImageEvent.canvasEventsList;
      75             for(k in evns) obj._eventList[k] = [];
      76         }
      77             
      78     }
      79 
      80     static canvasEventsList = {
      81         down: "onpointerdown",
      82         move: "onpointermove",
      83         up: "onpointerup",
      84         click: "onclick",
      85         wheel: "onmousewheel",
      86         out: "pointermove", //移出 
      87         over: "pointermove", //移入
      88     }
      89 
      90     static isEventName(eventName){
      91 
      92         return CanvasImageEvent.canvasEventsList[eventName] !== undefined;
      93 
      94     }
      95     
      96     static emptyBox = new Box();
      97     static emptyRotate = new Rotate();
      98 
      99     constructor(domElement, domElementPos, box, maxSize){
     100         this._running = "";
     101         this._delList = [];
     102         this.initEvent(domElement, domElementPos, box, maxSize);
     103         CanvasImageEvent.bind(this);
     104     }
     105 
     106     initEvent(domElement, domElementPos, box, maxSize = null){
     107         this.disposeEvent();
     108 
     109         if(CanvasImageRender.prototype.isPrototypeOf(domElement)){
     110             this.domElement = domElement.domElement;
     111             this.domElementPos = domElement.domElementPos;
     112             this.box = domElement.box;
     113             this.maxSize = domElement.maxSize;
     114         }
     115 
     116         else{
     117             this.domElement = domElement;
     118             this.domElementPos = domElementPos;
     119             this.box = box;
     120             this.maxSize = maxSize;
     121         }
     122 
     123         if(this._eventList !== undefined){
     124             for(let evn in this._eventList){
     125                 if(this._eventList[evn] !== undefined) this._createEvent(evn);
     126             }
     127 
     128         }
     129         
     130         return this;
     131     }
     132 
     133     add(ca, eventName, callback){
     134         if(CanvasImageEvent.canvasEventsList[eventName] === undefined) return console.warn("CanvasImageEvent: 参数错误 "+ eventName);
     135         if(typeof callback !== "function") return console.warn("CanvasImageEvent: 事件添加失败,参数错误");
     136         
     137         this._add(ca, eventName);
     138         this._addCA(ca, eventName, callback);
     139         
     140         return ca;
     141     }
     142     
     143     remove(ca, eventName, callback){
     144         if(CanvasImageEvent.canvasEventsList[eventName] === undefined) return console.warn("CanvasImageEvent: 参数错误 "+ eventName);
     145         if(typeof callback !== "function") return console.warn("CanvasImageEvent: 事件添加失败,参数错误");
     146         if(this._running !== eventName){
     147             this._remove(ca, eventName);
     148             this._removeCA(ca, eventName, callback);
     149         }
     150 
     151         else this._delList.push(ca, eventName, callback);
     152 
     153         return ca;
     154     }
     155 
     156     disposeEvent(eventName){
     157         if(eventName === "over" || eventName === "out"){
     158 
     159             if(typeof this["_"+eventName] === "function"){
     160                 this.domElement.removeEventListener(CanvasImageEvent.canvasEventsList[eventName], this["_"+eventName]);
     161                 delete this["_"+eventName];
     162             }
     163 
     164         }
     165 
     166         else{
     167 
     168             if(typeof this["_over"] === "function"){
     169                 this.domElement.removeEventListener(CanvasImageEvent.canvasEventsList["over"], this["_over"]);
     170                 delete this["_over"];
     171             }
     172 
     173             if(typeof this["_out"] === "function"){
     174                 this.domElement.removeEventListener(CanvasImageEvent.canvasEventsList["out"], this["_out"]);
     175                 delete this["_out"];
     176             }
     177 
     178         }
     179 
     180         return this;
     181     }
     182     
     183     clear(ca, eventName){
     184         if(eventName === undefined){
     185             var k; for(k in this._eventList){
     186                 this._remove(ca, k);
     187             }
     188             
     189             if(ca._eventList !== undefined) delete ca._eventList; //CanvasImageEvent.bind(ca, true);
     190             
     191         }
     192 
     193         else if(CanvasImageEvent.canvasEventsList[eventName] !== undefined){
     194             this._remove(ca, eventName);
     195 
     196             if(ca._eventList !== undefined) ca._eventList[eventName].length = 0;
     197             
     198         }
     199 
     200         return ca;
     201     }
     202 
     203     _addCA(ca, eventName, callback){
     204         if(ca._eventList === undefined) CanvasImageEvent.bind(ca);
     205         if(ca._eventList[eventName] === undefined) ca._eventList[eventName] = [];
     206         ca._eventList[eventName].push(callback);
     207 
     208     }
     209 
     210     _removeCA(ca, eventName, callback){
     211         if(ca._eventList !== undefined && ca._eventList[eventName] !== undefined){
     212             for(let k = 0, len = ca._eventList[eventName].length; k < len; k++){
     213                 if(ca._eventList[eventName][k] === callback){
     214                     ca._eventList[eventName].splice(k, 1);
     215                     break;
     216                 }
     217             }
     218         }
     219 
     220     }
     221 
     222     _add(ca, eventName){
     223         if(this._eventList[eventName] === undefined){
     224             this._eventList[eventName] = [];
     225             this._createEvent(eventName);
     226         }
     227 
     228         //if(this._eventList[eventName].includes(ca) === false) this._eventList[eventName].push(ca);
     229         this._eventList[eventName].push(ca);
     230     }
     231 
     232     _remove(ca, eventName){
     233         if(this._eventList[eventName] !== undefined){
     234             let key = this._eventList[eventName].indexOf(ca);
     235             if(key !== -1) this._eventList[eventName].splice(key, 1);
     236             if(key === 0){
     237                 if(eventName == "over" || eventName === "out") this.disposeEvent(eventName);
     238                 else this.domElement[CanvasImageEvent.canvasEventsList[eventName]] = null;
     239                 delete this._eventList[eventName];
     240             }
     241 
     242         }
     243 
     244     }
     245 
     246     _createEvent(evn){
     247         var k, len, ca, arr, tar = null, oldTar = null, _run = null, pageX, pageY;
     248 
     249         const _box = CanvasImageEvent.emptyBox, _rotate = CanvasImageEvent.emptyRotate,
     250 
     251         run = event => {
     252             len = this["_eventList"][evn].length;
     253             if(len === 0) return;
     254 
     255             pageX = event.pageX - this.domElementPos.x + this.box.x;
     256             pageY = event.pageY - this.domElementPos.y + this.box.y;
     257             
     258             tar = null;
     259             for(k = 0; k < len; k++){
     260                 ca = this["_eventList"][evn][k];
     261                 _box.copy(ca.box);
     262                 
     263                 //计算旋转后的box
     264                 if(ca.rotate !== null) _box.setFromRotate(_rotate.set(ca.x+ca.rotate.origin.x, ca.y+ca.rotate.origin.y, ca.rotate.angle));
     265 
     266                 if(ca["visible"] === true && _box.containsPoint(pageX, pageY)){
     267                     
     268                     if(tar === null || tar["index"] < ca["index"]) tar = ca;
     269                     
     270                 }
     271 
     272             }
     273             
     274             if(_run !== null) _run();
     275             if(tar !== null){
     276                 this._running = evn;
     277                 arr = tar["_eventList"][evn]; 
     278                 len = arr.length;
     279                 for(k = 0; k < len; k++) arr[k](event, tar);
     280                 tar = null;
     281 
     282                 len = this._delList.length;
     283                 for(k = 0; k < len; k += 3){
     284                     this._remove(this._delList[k], this._delList[k+1]);
     285                     this._removeCA(this._delList[k], this._delList[k+1], this._delList[k+2]);
     286                 }
     287                 this._running = "";
     288                 this._delList.length = 0;
     289             }
     290             
     291         }
     292         
     293         if(evn === "over" || evn === "out"){
     294             this.domElement.addEventListener(CanvasImageEvent.canvasEventsList[evn], run);
     295             this["_"+evn] = run;
     296             if(evn === "over"){
     297                 _run = ()=>{
     298                     if(tar !== null){
     299                         if(oldTar !== null){
     300                             if(oldTar !== tar) oldTar = tar;
     301                             else tar = null;
     302                         }
     303                         else oldTar = tar;
     304                     }
     305                     else if(oldTar !== null) oldTar = null;
     306     
     307                 }
     308 
     309             }
     310 
     311             else{
     312                 let _tar = null;
     313                 _run = ()=>{
     314                     if(tar !== null){
     315                         if(oldTar !== null){
     316                             if(oldTar !== tar){
     317                                 _tar = tar;
     318                                 tar = oldTar;
     319                                 oldTar = _tar;
     320                             }
     321                             else tar = null;
     322                         }
     323                         else{
     324                             oldTar = tar;
     325                             tar = null;
     326                         }
     327                     }
     328                     else if(oldTar !== null){
     329                         tar = oldTar;
     330                         oldTar = null;
     331                     }
     332     
     333                 }
     334 
     335             }
     336 
     337             /* _run = ()=>{
     338                 if(tar !== null){
     339                     if(oldTar !== null){
     340 
     341                         if(oldTar !== tar){
     342                             if(evn === "over") oldTar = tar;
     343                             else{
     344                                 let _tar = tar;
     345                                 tar = oldTar;
     346                                 oldTar = _tar;
     347                             }
     348                         }
     349 
     350                         else tar = null;
     351 
     352                     }
     353 
     354                     else{
     355                         oldTar = tar;
     356                         if(evn === "out") tar = null;
     357                         
     358                     }
     359                     
     360                 }
     361 
     362                 else{
     363                     if(oldTar !== null){
     364                         if(evn === "out") tar = oldTar;
     365                         oldTar = null;
     366                     }
     367                     
     368                 }
     369 
     370             } */
     371             
     372         }
     373 
     374         else this.domElement[CanvasImageEvent.canvasEventsList[evn]] = run;
     375 
     376     }
     377 
     378 }
     379 
     380 
     381 
     382 
     383 /* CanvasImageRender (渲染 CanvasImage)
     384 parameter: 
     385     option = {
     386         canvas             //默认 新的canvas
     387         width, height     //默认 1
     388         className, id    //默认 ""
     389 
     390         //一下属性 scroll 为 true 才有效
     391         scroll             //是否启用滚动轴; 默认 false
     392         scrollSize        //滚动轴的宽或高; 默认 6; (如果想隐藏滚动条最好用 scrollVisible, 而不是把此属性设为0)
     393         scrollVisible    //滚动轴的显示类型; 可能值 "auto" || "visible" || 默认"";
     394 
     395         scrollEventType    //可能的值: 默认"", "default", "touch";
     396         inertia            //是否启用移动端滚动轴的惯性; 默认 false
     397         inertiaLife        //移动端滚动轴惯性生命(阻力强度); 0 - 1; 默认 0.04;
     398     }
     399 
     400 attribute:
     401 method:
     402 
     403 ImageData:
     404     length: w * h * 4
     405     r: 0 - 255
     406     g: 0 - 255
     407     b: 0 - 255
     408     a: 0 - 255 Math.round(255 * a)
     409 
     410     遍历:
     411     const data = context.getImageData(0, 0, canvas.width, canvas.height).data;
     412     for(let k = 0, x, y, r, g, b, a, i; k < len; k++){
     413         x = k % width;
     414         y = Math.floor(k / width);
     415 
     416         i = k*4;
     417         r = data[i]
     418         g = data[i+1]
     419         b = data[i+2]
     420         a = data[i+3]
     421 
     422         console.log(x, y, r, g, b, a, i);
     423     }
     424     
     425 demo:
     426     //CanvasImageRender
     427     const cir = new CanvasImageRender({
     428          WORLD.width, 
     429         height: WORLD.height,
     430 
     431         //一下属性 scroll 为 true 才有效
     432         scroll: true,             //是否启用滚动轴; 默认 false
     433         scrollSize: 10,        //滚动轴的宽或高; 默认 6;
     434         scrollVisible: "auto",    //滚动轴的显示类型; 可能值 默认"auto" || "visible" || ""; (scroll视图不影响scroll的运行,所以在移动端可以把视图干掉既 scrollVisible: "")
     435 
     436         scrollEventType: "pc",    //可能的值: 默认 disable 禁用, pc, mobile;
     437         //inertia: true,            //移动端滚动轴是否启用惯性; 默认 false
     438         //inertiaLife: 0.04,        //移动端滚动轴惯性生命(阻力强度); 0 - 1; 默认 0.04;
     439     });
     440     cir.domElement.style = `
     441         position: absolute;
     442         z-index: 9999;
     443         background: rgb(127,127,127);
     444     `;
     445 
     446     const ciA = cir.add(new CanvasImage()).pos(59, 59).load("view/examples/img/test.png", cir),
     447     pi2 = Math.PI*2;
     448 
     449     for(let k = 0, ca; k < 1000000; k++){
     450         ca = new CanvasImage(ciA);
     451         cir.list.push(ca);
     452         ca.pos(UTILS.random(0, 1000000), UTILS.random(0, 1000000));
     453         ca.rotate = new Rotate(0, 0, UTILS.random(0, pi2));
     454     }
     455     
     456     //为什么要用 initList() ? 因为for循环中是直接把ca加入到了列表, 没对ca做任何初始化操作;
     457     //如果全用.add() 就不需要在调用 initList(); 但 .add() 方法不适合一次性添加多个 CanvasImage;
     458     cir.initList(); 
     459 
     460 */
     461 class CanvasImageRender{
     462 
     463     static emptyArrA = []
     464     static emptyArrB = []
     465     static paramCon = {alpha: true}
     466 
     467     static defaultStyles = {
     468         filter: "none",
     469         globalAlpha: 1,
     470         globalCompositeOperation: "source-over",
     471         imageSmoothingEnabled: true,
     472         miterLimit: 10,
     473         font: "12px SimSun, Songti SC",
     474         textAlign: "left",
     475         textBaseline: "top",
     476         lineCap: "butt",
     477         lineJoin: "miter",
     478         lineDashOffset: 0,
     479         lineWidth: 1,
     480         shadowColor: "rgba(0, 0, 0, 0)",
     481         shadowBlur: 0,
     482         shadowOffsetX: 0,
     483         shadowOffsetY: 0,
     484         fillStyle: "rgba(0,0,0,0.6)",
     485         strokeStyle: "#ffffff",
     486     }
     487 
     488     static setDefaultStyles(context){
     489         let k = "", styles = CanvasImageRender.defaultStyles;
     490         for(k in styles){
     491             if(context[k] !== styles[k]) context[k] = styles[k];
     492         }
     493     }
     494 
     495     static getContext(canvas, className, id){
     496         if(CanvasImageRender.isCanvas(canvas) === false) canvas = document.createElement("canvas");
     497         const context = canvas.getContext("2d", CanvasImageRender.paramCon);
     498 
     499         if(typeof className === "string") canvas.className = className;
     500         if(typeof id === "string") canvas.setAttribute('id', id);
     501         
     502         return context;
     503     }
     504 
     505     static downloadImage(func){
     506         const input = document.createElement("input");
     507         input.type = "file";
     508         input.multiple = "multiple";
     509         input.accept = ".png, .jpg, .jpeg, .bmp, .gif";
     510     
     511         input.onchange = e => {
     512             if(e.target.files.length === 0) return;
     513             const fr = new FileReader();
     514             fr.onloadend = e1 => {
     515                 const img = new Image();
     516                 img.onload = () => func(img);
     517                 img.src = e1.target.result;
     518             }
     519 
     520             fr.readAsDataURL(e.target.files[0]);
     521         }
     522         
     523         input.click();
     524     }
     525 
     526     static isCanvasImage(img){ //OffscreenCanvas:ImageBitmap; CSSImageValue:
     527 
     528         return ImageBitmap["prototype"]["isPrototypeOf"](img) || 
     529         HTMLImageElement["prototype"]["isPrototypeOf"](img) || 
     530         HTMLCanvasElement["prototype"]["isPrototypeOf"](img) || 
     531         CanvasRenderingContext2D["prototype"]["isPrototypeOf"](img) || 
     532         HTMLVideoElement["prototype"]["isPrototypeOf"](img);
     533         
     534     }
     535 
     536     static isCanvas(canvas){
     537         
     538         return HTMLCanvasElement["prototype"]["isPrototypeOf"](canvas);
     539 
     540     }
     541 
     542     #canvasImageEvent = new CanvasImageEvent();
     543     #eventDispatcher = new EventDispatcher();
     544     #box = new Box();
     545     #length = 0;
     546     get box(){return this.#box;}
     547     get length(){return this.#length;}
     548     
     549     #maxSize = null;
     550     get maxSize(){return this.#maxSize;}
     551 
     552     constructor(option = {}){
     553         this.list = [];
     554         this.domElementPos = new Point();
     555         this.context = CanvasImageRender.getContext(option.canvas, option.className, option.id);
     556         this.domElement = this.context.canvas;
     557 
     558         let _x = this.#box.x, _y = this.#box.y;
     559         Object.defineProperties(this.#box, {
     560 
     561             x: {
     562                 get: () => {return _x;},
     563                 set: v => {
     564                     if(v < 0 || this.#maxSize === null) v = 0;
     565                     else if(v + this.#box.w > this.#maxSize.x) v = this.#maxSize.x - this.#box.w;
     566                     _x = v;
     567                 }
     568             },
     569 
     570             y: {
     571                 get: () => {return _y;},
     572                 set: v => {
     573                     if(v < 0 || this.#maxSize === null) v = 0;
     574                     else if(v + this.#box.h > this.#maxSize.y) v = this.#maxSize.y - this.#box.h;
     575                     _y = v;
     576                 }
     577             },
     578 
     579         });
     580 
     581         //event
     582         this.#canvasImageEvent.initEvent(this);
     583         const eventParam = {target: this}, 
     584         eventParam1 = {target: this, value: null}
     585         this.#eventDispatcher
     586         .customEvents("pos", eventParam)
     587         .customEvents("initList", eventParam)
     588         .customEvents("size", eventParam)
     589         .customEvents("add", eventParam1)
     590         .customEvents("remove", eventParam1);
     591 
     592         //scroll
     593         if(option.scroll === true){
     594             this.#maxSize = new Point();
     595             this.scrollSize = UTILS.isNumber(option.scrollSize) ? option.scrollSize : 6;
     596 
     597             switch(option.scrollVisible){
     598                 case "visible": 
     599                 case "auto": 
     600                 this.scrollVisible = option.scrollVisible;
     601                 break;
     602 
     603                 default: 
     604                 this.scrollVisible = "";
     605                 break;
     606             }
     607     
     608             switch(option.scrollEventType){
     609                 case "default": 
     610                 this._createScrollEventPC();
     611                 break;
     612 
     613                 case "touch": 
     614                 this._createScrollEventMobile(option.inertia, option.inertiaLife);
     615                 break;
     616 
     617                 default: 
     618                 break;
     619             }
     620         }
     621 
     622         this.size(option.width, option.height);
     623     }
     624 
     625     addEvent(ca, eventName, callback){
     626         if(ca) this.#canvasImageEvent.add(ca, eventName, callback);
     627         else this.#eventDispatcher.register(eventName, callback);
     628     }
     629 
     630     removeEvent(ca, eventName, callback){
     631         if(ca) this.#canvasImageEvent.remove(ca, eventName, callback);
     632         else this.#eventDispatcher.deleteEvent(eventName, callback);
     633     }
     634 
     635     isDraw(ca){
     636         return ca["visible"] === true && ca["image"] !== null && this["box"]["intersectsBox"](ca["box"]);
     637     }
     638 
     639     updateCanvasRect(){
     640         const rect = this.domElement.getBoundingClientRect();
     641         this.domElementPos.set(rect.x, rect.y);
     642     }
     643 
     644     pos(x = 0, y = 0){
     645         this.domElement.style.left = x + "px";
     646         this.domElement.style.top = y + "px";
     647         if(this.domElement.parentElement !== null) this.updateCanvasRect();
     648         this.#eventDispatcher.trigger("pos");
     649         return this;
     650     }
     651 
     652     size(w = 1, h = 1){
     653         this.domElement.width = w;
     654         this.domElement.height = h;
     655         CanvasImageRender.setDefaultStyles(this.context);
     656         this.box.size(w, h);
     657         this.#eventDispatcher.trigger("size");
     658         return this;
     659     }
     660 
     661     add(ca){
     662         if(CanvasImage.prototype.isPrototypeOf(ca) && !this.list.includes(ca)){
     663             const len = this.list.length;
     664             
     665             if(this.list[ca.index] === undefined){
     666                 ca.index = len;
     667                 this.list.push(ca);
     668             }
     669 
     670             else{
     671                 const arr = this.list.splice(ca.index);
     672                 this.list.push(ca);
     673                 for(let k = 0, c = arr.length; k < c; k++){
     674                     this.list.push(arr[k]);
     675                     arr[k].index++;
     676                 }
     677             }
     678 
     679             if(this.#maxSize !== null) this._bindScroll(ca);
     680             this.#length++;
     681             this.#eventDispatcher.trigger("add", param => param.value = ca);
     682         }
     683         
     684         return ca;
     685     }
     686 
     687     remove(ca){
     688         var i = ca.index;
     689 
     690         if(this.list[i] !== ca) i = this.list.indexOf(ca);
     691 
     692         if(i !== -1){
     693             this.list.splice(i, 1);
     694             for(let k = i, len = this.list.length; k < len; k++) this.list[k].index -= 1;
     695             if(this.#maxSize !== null) this._unbindScroll(ca);
     696             this.#length--;
     697             this.#eventDispatcher.trigger("remove", param => param.value = ca);
     698         }
     699 
     700         return ca;
     701     }
     702 
     703     render(parentElem = document.body){
     704         this.redraw();
     705         parentElem.appendChild(this.domElement);
     706         this.updateCanvasRect();
     707         return this;
     708     }
     709 
     710     initList(i){
     711         if(i === undefined || i < 0) i = 0;
     712         this.#length = this.list.length;
     713 
     714         for(let k = i; k < this.#length; k++) this.list[k].index = k;
     715 
     716         if(this.#maxSize !== null){
     717             this._resetMaxSizeX();
     718             this._resetMaxSizeY();
     719         }
     720         
     721         this.#eventDispatcher.trigger("initList");
     722         return this;
     723     }
     724 
     725     clear(){
     726 
     727         this['context']['clearRect'](0, 0, this['box']['w'], this['box']['h']);
     728 
     729     }
     730 
     731     draw(){
     732         const len = this["list"]["length"];
     733         for(let k = 0, ca; k < len; k++){
     734             ca = this["list"][k];
     735             if(this["isDraw"](ca) === true) this["_draw"](ca);
     736         }
     737         
     738         if(this.#maxSize !== null && this.scrollVisible !== "") this._drawScroll();
     739     }
     740 
     741     clearTarget(ca){
     742         this["_computeOverlap"](ca);
     743         ca["overlap"]["draw"] = false;
     744         this["_drawTarget"](ca);
     745     }
     746 
     747     drawTarget(ca){
     748         this["_computeOverlap"](ca);
     749         this["_drawTarget"](ca);
     750     }
     751 
     752     redraw(){
     753         this.clear();
     754         this.draw();
     755     }
     756 
     757 
     758     //限内部使用
     759     _computeOverlap(tar){ //不支持带有旋转的 CanvasImage
     760         //1 如果检索到与box相交的ca时, 合并其box执行以下步骤: 
     761         //2 检索 已检索过的 并且 没有相交的ca
     762         //3 如果存在相交的ca就合并box并继续第 2 步; 如果不存在继续第 1 步
     763         if(tar["overlap"] === null) tar["overlap"] = {box: new Box(), draw: false};
     764         const _list = CanvasImageRender.emptyArrA, list = CanvasImageRender.emptyArrB, len = this.list.length, box = tar["overlap"]["box"]["copy"](tar["box"]);
     765 
     766         for(let k = 0, i = 0, c = 0, _c = 0, a = _list, b = list, loop = false; k < len; k++){
     767             tar = this["list"][k];
     768             if(tar["overlap"] === null) tar["overlap"] = {box: new Box(), draw: false};
     769             
     770             if(tar.rotate !== null || this.isDraw(tar) === false){
     771                 if(tar["overlap"]["draw"] !== false) tar["overlap"]["draw"] = false;
     772                 continue;
     773             }
     774 
     775             if(box["intersectsBox"](tar["box"]) === true){
     776                 if(tar["overlap"]["draw"] !== true) tar["overlap"]["draw"] = true;
     777                 box["expand"](tar["box"]);
     778                 loop = true;
     779 
     780                 while(loop === true){
     781                     b["length"] = 0;
     782                     loop = false;
     783                     c = _c;
     784 
     785                     for(i = 0; i < c; i++){
     786                         tar = a[i];
     787 
     788                         if(box["intersectsBox"](tar["box"]) === true){
     789                             if(tar["overlap"]["draw"] !== true) tar["overlap"]["draw"] = true;
     790                             box["expand"](tar["box"]);
     791                             loop = true; _c--;
     792                         }
     793 
     794                         else b.push(tar);
     795                         
     796                     }
     797 
     798                     a = a === _list ? list : _list;
     799                     b = b === _list ? list : _list;
     800 
     801                 }
     802                 
     803             }
     804 
     805             else{
     806                 _c++;
     807                 a["push"](tar);
     808                 if(tar["overlap"]["draw"] !== false) tar["overlap"]["draw"] = false;
     809             }
     810 
     811         }
     812         
     813         _list.length = list.length = 0;
     814     }
     815 
     816     _drawImage(ca, ox = 0, oy = 0){
     817         if(ca["opacity"] === 1){
     818             if(ca.isScale === false) this["context"]["drawImage"](ca["image"], ca["box"]["x"]-this.#box.x+ox, ca["box"]["y"]-this.#box.y+oy);
     819             else if(ca.scale === null) this["context"]["drawImage"](ca["image"], ca["box"]["x"]-this.#box.x+ox, ca["box"]["y"]-this.#box.y+oy, ca.box.w, ca.box.h);
     820             //else this["context"]["drawImage"](ca["image"], ca.scale.x+ca.box.x, ca.scale.y+ca.box.y, ca.scale.w, ca.scale.h);
     821         }
     822 
     823         else{
     824             this["context"]["globalAlpha"] = ca["opacity"];
     825             if(ca.isScale === false) this["context"]["drawImage"](ca["image"], ca["box"]["x"]-this.#box.x+ox, ca["box"]["y"]-this.#box.y+oy);
     826             else if(ca.scale === null) this["context"]["drawImage"](ca["image"], ca["box"]["x"]-this.#box.x+ox, ca["box"]["y"]-this.#box.y+oy, ca.box.w, ca.box.h);
     827             //else this["context"]["drawImage"](ca["image"], ca.scale.x, ca.scale.y, ca.scale.w, ca.scale.h);
     828             this["context"]["globalAlpha"] = 1;
     829         }
     830     }
     831 
     832     _draw(ca){
     833         if(ca.rotate === null) this._drawImage(ca);
     834         else{
     835             const cx = ca.rotate.origin.x + ca.box.x - this.#box.x, 
     836             cy = ca.rotate.origin.y + ca.box.y - this.#box.y;
     837             this.context.translate(cx, cy);
     838             this.context.rotate(ca.rotate.angle);
     839             this._drawImage(ca, -cx, -cy);
     840             this.context.rotate(-ca.rotate.angle);
     841             this.context.translate(-cx, -cy);
     842         }
     843     }
     844     
     845     _drawTarget(ca){
     846         const len = this["list"]["length"];
     847         this["context"]["clearRect"](ca["overlap"]["box"]["x"], ca["overlap"]["box"]["y"], ca["overlap"]["box"]["w"], ca["overlap"]["box"]["h"]);
     848 
     849         for(let k = 0; k < len; k++){
     850             ca = this["list"][k];
     851             if(ca["overlap"]["draw"] === true) this._draw(ca);
     852         }
     853 
     854         if(this.#maxSize !== null && this.scrollVisible !== "") this._drawScroll();
     855     }
     856 
     857     //关于scroll
     858     _drawScroll(){
     859         if(this.scrollVisible === "visible" || (this.scrollVisible === "auto" && this.#maxSize.x > this.#box.w)){
     860             this.context.fillRect(this._cursorX(), this.#box.h - this.scrollSize, this.#maxSize.x <= this.#box.w ? this.#box.w : this.#box.w / this.#maxSize.x * this.#box.w, this.scrollSize);
     861         }
     862 
     863         if(this.scrollVisible === "visible" || (this.scrollVisible === "auto" && this.#maxSize.y > this.#box.h)){
     864             this.context.fillRect(this.#box.w - this.scrollSize, this._cursorY(), this.scrollSize, this.#maxSize.y <= this.#box.h ? this.#box.h : this.#box.h / this.#maxSize.y * this.#box.h);
     865         }
     866     }
     867 
     868     _createScrollEventPC(){
     869         var dPos = -1;
     870 
     871         const setTop = (event, top) => {
     872             this.#box.y = top / this.box.h * this.#maxSize.y;
     873             this.redraw();
     874         },
     875 
     876         setLeft = (event, left) => {
     877             this.#box.x = left / this.box.w * this.#maxSize.x;
     878             this.redraw();
     879         },
     880         
     881         onMoveTop = event => {
     882             setTop(event, event.pageY - this.domElementPos.y - dPos);
     883         },
     884 
     885         onMoveLeft = event => {
     886             setLeft(event, event.pageX - this.domElementPos.x - dPos);
     887         },
     888 
     889         onUpTop = event => {
     890             document.body.removeEventListener('pointermove', onMoveTop);
     891             document.body.removeEventListener('pointerup', onUpTop);
     892 
     893             this.domElement.removeEventListener('pointermove', onMoveTop);
     894             this.domElement.removeEventListener('pointerup', onUpTop);
     895 
     896             if(event !== null) onMoveTop(event);
     897         },
     898 
     899         onUpLeft = event => {
     900             document.body.removeEventListener('pointermove', onMoveLeft);
     901             document.body.removeEventListener('pointerup', onUpLeft);
     902 
     903             this.domElement.removeEventListener('pointermove', onMoveLeft);
     904             this.domElement.removeEventListener('pointerup', onUpLeft);
     905 
     906             if(event !== null) onMoveLeft(event);
     907         },
     908 
     909         scope = this, _box = this.#box,
     910 
     911         //伪 CanvasImage, 是 CanvasImageEvent 使用的必须属性
     912         boxX = new Box(),
     913         eventTargetX = {
     914             get index(){return scope.length+1;},
     915             get visible(){return true;},
     916             get box(){return boxX},
     917             get rotate(){return null},
     918         },
     919         
     920         boxY = new Box(),
     921         eventTargetY = {
     922             get index(){return scope.length+2;},
     923             get visible(){return true;},
     924             get box(){return boxY},
     925             get rotate(){return null},
     926         },
     927 
     928         eventTargetWheel = {
     929             get index(){return -1;},
     930             get visible(){return true;},
     931             get box(){return _box},
     932             get rotate(){return null},
     933         }
     934 
     935         Object.defineProperties(boxX, {
     936             x: {get: () => {return _box.x;}},
     937             y: {get: () => {return _box.h - scope.scrollSize + _box.y;}},
     938             w: {get: () => {return _box.w;}},
     939             h: {get: () => {return scope.scrollSize;}},
     940         });
     941 
     942         Object.defineProperties(boxY, {
     943             x: {get: () => {return _box.w - scope.scrollSize + _box.x;}},
     944             y: {get: () => {return _box.y;}},
     945             w: {get: () => {return scope.scrollSize;}},
     946             h: {get: () => {return _box.h;}},
     947         });
     948 
     949         //pc event
     950         this.#canvasImageEvent.add(eventTargetX, "down", event => {
     951             dPos = event.pageX - this.domElementPos.x - this._cursorX();
     952             
     953             onUpLeft(null);
     954 
     955             document.body.addEventListener("pointermove", onMoveLeft);
     956             document.body.addEventListener("pointerup", onUpLeft);
     957 
     958             this.domElement.addEventListener("pointermove", onMoveLeft);
     959             this.domElement.addEventListener("pointerup", onUpLeft);
     960         });
     961 
     962         this.#canvasImageEvent.add(eventTargetY, "down", event => {
     963             dPos = event.pageY - this.domElementPos.y - this._cursorY();
     964 
     965             onUpTop(null);
     966 
     967             document.body.addEventListener("pointermove", onMoveTop);
     968             document.body.addEventListener("pointerup", onUpTop);
     969 
     970             this.domElement.addEventListener("pointermove", onMoveTop);
     971             this.domElement.addEventListener("pointerup", onUpTop);
     972         });
     973 
     974         this.#canvasImageEvent.add(eventTargetWheel, "wheel", event => {
     975             if(this.#maxSize.y > this.#box.h){
     976                 dPos = 50 / this.#maxSize.y * this.#box.h;
     977                 setTop(event, this._cursorY() + (event.wheelDelta === 120 ? -dPos : dPos));
     978             }
     979             else if(this.#maxSize.x > this.#box.w){
     980                 dPos = 50 / this.#maxSize.x * this.#box.w;
     981                 setLeft(event, this._cursorX() + (event.wheelDelta === 120 ? -dPos : dPos));
     982             }
     983         });
     984 
     985     }
     986 
     987     _createScrollEventMobile(inertia, inertiaLife){
     988         var px, py, dPos = 0, sPos = new Point(), step = 1, aniamteRun = false, stepA = "", stepB = "";
     989 
     990         if(inertia === true){
     991             inertiaLife = 1 - ((UTILS.isNumber(inertiaLife) && inertiaLife >= 0 && inertiaLife <= 1) ? inertiaLife : 0.04);
     992             var animate = new AnimateLoop(null), sTime = 0,
     993         
     994             inertiaTop = (event, speed) => {
     995                 if(Math.abs(speed) < 1) return;
     996                 
     997                 stepA = speed < 0 ? "-top" : "top";
     998                 if(aniamteRun && stepA === stepB) step += 0.3;
     999                 else{
    1000                     step = 1;
    1001                     stepB = stepA;
    1002                 }
    1003                 
    1004                 animate.start(() => {
    1005                     speed *= inertiaLife;
    1006                     setTop(event, this.#box.y + step * 20 * speed);
    1007                     if(Math.abs(speed) < 0.001 || this.#box.y <= 0 || this.#box.my >= this.#maxSize.y) animate.stop();
    1008                 });
    1009             },
    1010 
    1011             inertiaLeft = (event, speed) => {
    1012                 if(Math.abs(speed) < 1) return;
    1013                 stepA = speed < 0 ? "-left" : "left";
    1014                 if(aniamteRun && stepA === stepB) step += 0.3;
    1015                 else{
    1016                     step = 1;
    1017                     stepB = stepA;
    1018                 }
    1019                 animate.start(() => {
    1020                     speed *= inertiaLife;
    1021                     setLeft(event, this.#box.x + step * 20 * speed);
    1022                     if(Math.abs(speed) < 0.001 || this.#box.x <= 0 || this.#box.mx >= this.#maxSize.x) animate.stop();
    1023                 });
    1024             }
    1025         }
    1026 
    1027         const setTop = (event, top) => {
    1028             this.#box.y = top;
    1029             this.redraw();
    1030         },
    1031 
    1032         setLeft = (event, left) => {
    1033             this.#box.x = left;
    1034             this.redraw();
    1035         },
    1036         
    1037         onMoveTop = event => {
    1038             setTop(event, dPos - event.pageY - this.domElementPos.y);
    1039         },
    1040 
    1041         onMoveLeft = event => {
    1042             setLeft(event, dPos - event.pageX - this.domElementPos.x);
    1043         },
    1044 
    1045         onUpTop = event => {
    1046             this.domElement.removeEventListener('pointermove', onMove);
    1047 
    1048             document.body.removeEventListener('pointermove', onMoveTop);
    1049             document.body.removeEventListener('pointerup', onUpTop);
    1050 
    1051             this.domElement.removeEventListener('pointermove', onMoveTop);
    1052             this.domElement.removeEventListener('pointerup', onUpTop);
    1053 
    1054             if(event !== null){
    1055                 onMoveTop(event);
    1056                 if(inertia === true) inertiaTop(event, (this.#box.y - sPos.y) / (Date.now() - sTime));
    1057             }
    1058         },
    1059 
    1060         onUpLeft = event => {
    1061             this.domElement.removeEventListener('pointermove', onMove);
    1062 
    1063             document.body.removeEventListener('pointermove', onMoveLeft);
    1064             document.body.removeEventListener('pointerup', onUpLeft);
    1065 
    1066             this.domElement.removeEventListener('pointermove', onMoveLeft);
    1067             this.domElement.removeEventListener('pointerup', onUpLeft);
    1068 
    1069             if(event !== null){
    1070                 onMoveLeft(event);
    1071                 if(inertia === true) inertiaLeft(event, (this.#box.x - sPos.x) / (Date.now() - sTime));
    1072             }
    1073         },
    1074 
    1075         _box = this.#box,
    1076 
    1077         eventTarget = {
    1078             get index(){return -1;}, //最低优先
    1079             get visible(){return true;},
    1080             get box(){return _box;},
    1081             get rotate(){return null},
    1082         },
    1083 
    1084         a1 = Math.PI / 4, a2 = Math.PI / 2 + a1,
    1085 
    1086         onUp = event => {
    1087             this.domElement.removeEventListener("pointerup", onUp);
    1088             this.domElement.removeEventListener('pointermove', onMove);
    1089         },
    1090 
    1091         onMove = event => {
    1092             dPos++; if(dPos < 10) return;
    1093             onUp(event);
    1094             const a = Math.atan2(event.pageY - py, event.pageX - px);
    1095             if((a < a2 && a >= a1) || (a < -a1 && a >= -a2)){ //y轴
    1096                 if(this.#maxSize.y > this.#box.h){
    1097                     dPos = py - this.domElementPos.y + sPos.y;
    1098                     document.body.addEventListener("pointermove", onMoveTop);
    1099                     document.body.addEventListener("pointerup", onUpTop);
    1100                 }
    1101             }
    1102             else{ //x轴
    1103                 if(this.#maxSize.x > this.#box.w){
    1104                     dPos = px - this.domElementPos.x + sPos.x;
    1105                     document.body.addEventListener("pointermove", onMoveLeft);
    1106                     document.body.addEventListener("pointerup", onUpLeft);
    1107                 }
    1108             }
    1109         }
    1110     
    1111         this.#canvasImageEvent.add(eventTarget, "down", event => {
    1112             px = event.pageX;
    1113             py = event.pageY;
    1114             dPos = 0;
    1115             sPos.set(this.#box.x, this.#box.y);
    1116             sTime = Date.now();
    1117 
    1118             if(inertia === true){
    1119                 aniamteRun = animate.running;
    1120                 animate.stop();
    1121             }
    1122 
    1123             //防止没触发 move 或 up 事件;
    1124             onUpLeft(null);
    1125             onUpTop(null);
    1126             this.domElement.addEventListener("pointermove", onMove);
    1127             this.domElement.addEventListener("pointerup", onUp);
    1128         });
    1129     }
    1130 
    1131     _bindScroll(ca){
    1132         ca.__bindObj.writeBoxX = (nm, om) => {
    1133             if(nm > this.#maxSize.x) this.#maxSize.x = nm;
    1134             else if(nm < this.#maxSize.x){
    1135                 if(om >= this.#maxSize.x) this._resetMaxSizeX();
    1136             }
    1137         };
    1138 
    1139         ca.__bindObj.writeBoxY = (nm, om) => {
    1140             if(nm > this.#maxSize.y) this.#maxSize.y = nm;
    1141             else if(nm < this.#maxSize.y){
    1142                 if(om >= this.#maxSize.y) this._resetMaxSizeY();
    1143             }
    1144         };
    1145 
    1146         ca.__bindObj.writeBoxX(ca.box.mx);
    1147         ca.__bindObj.writeBoxY(ca.box.my);
    1148     }
    1149 
    1150     _unbindScroll(ca){
    1151         ca.__bindObj.writeBoxX = null;
    1152         ca.__bindObj.writeBoxY = null;
    1153         if(ca.box.mx >= this.#maxSize.x) this._resetMaxSizeX();
    1154         if(ca.box.my >= this.#maxSize.y) this._resetMaxSizeY();
    1155     }
    1156 
    1157     _resetMaxSizeX(){
    1158         this.#maxSize.x = 0;
    1159         for(let k = 0, len = this.list.length, m; k < len; k++){
    1160             m = this.list[k].box.mx;
    1161             if(m > this.#maxSize.x) this.#maxSize.x = m;
    1162         }
    1163     }
    1164 
    1165     _resetMaxSizeY(){
    1166         this.#maxSize.y = 0;
    1167         for(let k = 0, len = this.list.length, m; k < len; k++){
    1168             m = this.list[k].box.my;
    1169             if(m > this.#maxSize.y) this.#maxSize.y = m;
    1170         }
    1171     }
    1172 
    1173     _cursorX(v = this.#box.x){
    1174         return v/this.#maxSize.x*this.#box.w;
    1175     }
    1176 
    1177     _cursorY(v = this.#box.y){
    1178         return v/this.#maxSize.y*this.#box.h;
    1179     }
    1180 
    1181 }
    1182 
    1183 
    1184 
    1185 
    1186 /* CanvasImage 
    1187 parameter: 
    1188     image (构造器会调用一次 .setImage(image) 来处理 image 参数)
    1189 
    1190 attribute:
    1191     opacity: Float;     //透明度; 值0至1之间; 默认1;
    1192     visible: Boolean;    //默认true; 完全隐藏(既不绘制视图, 也不触发绑定的事件)
    1193     box: Box;             //.x.y 图像的位置, .w.h 图像的宽高;
    1194     rotate: Roate;        //旋转; 默认 null (注意: 旋转的原点是相对于 box.x.y 的)
    1195     此属性暂未实现//scale: Box;            //缩放; .x.y中心点, .w.h缩放; 默认 null; (注意: 缩放的原点是相对于 box.x.y 的)
    1196     x, y: Number;        //this.box 的 .x.y
    1197 
    1198     //以下属性不建议直接修改
    1199     overlap: Object; //CanvasImageRender.computeOverlaps(ca) 方法更新
    1200     index: Integer; //CanvasImageRender.index(ca) 修改
    1201 
    1202     //只读
    1203     width, height, image; isRotate, isScale
    1204 
    1205 method:
    1206     setImage(image): this;         //设置图像 (image 如果是 CanvasImage 并它正在加载图像时, 则会在它加载完成时自动设置 image);
    1207     load(src, onload): this;    //加载并设置图像 (onload 如果是 CanvasImageRender 则加载完后自动调用一次 redraw 或 render 方法);
    1208     pos(x, y): this;             //设置位置; x 可以是: Number, Object{x,y}
    1209 
    1210 demo:
    1211     //CanvasImageRender
    1212     const cir = new CanvasImageRender({ WORLD.width, height: WORLD.height});
    1213     cir.domElement.style = `
    1214         position: absolute;
    1215         z-index: 9999;
    1216         background: rgb(127,127,127);
    1217     `;
    1218 
    1219     //values
    1220     const ciA = cir.add(new CanvasImage()).pos(59, 59).load("view/examples/img/test.png", cir);
    1221     ciA.opacity = 0.2; //设置透明度
    1222 
    1223     const ciB = cir.add(new CanvasImage(ciA)).pos(59, 120);
    1224     ciB.rotate = new Rotate().toAngle(45); //旋转45度
    1225 
    1226     //event
    1227     const cie = new CanvasImageEvent(cir); //注意: CanvasImage 的缩放和旋转都会影响到 CanvasImageEvent
    1228     cie.add(ciB, "click", event => { //ciB 添加 点击事件
    1229         console.log("click ciB: ", event);
    1230     });
    1231 */
    1232 class CanvasImage{
    1233 
    1234     static bindBox(obj, box){
    1235         var _x = box.x, _y = box.y, _w = box.w, _h = box.h, oldV;
    1236 
    1237         Object.defineProperties(box, {
    1238 
    1239             x: {
    1240                 get: () => {return _x;},
    1241                 set: v => {
    1242                     oldV = _x;
    1243                     _x = v;
    1244                     if(obj.writeBoxX !== null) obj.writeBoxX(v+_w, oldV+_w);
    1245                 }
    1246             },
    1247 
    1248             y: {
    1249                 get: () => {return _y;},
    1250                 set: v => {
    1251                     oldV = _y;
    1252                     _y = v;
    1253                     if(obj.writeBoxY !== null) obj.writeBoxY(v+_h, oldV+_h);
    1254                 }
    1255             },
    1256 
    1257             w: {
    1258                 get: () => {return _w;},
    1259                 set: v => {
    1260                     oldV = _w;
    1261                     _w = v;
    1262                     if(obj.writeBoxX !== null) obj.writeBoxX(v+_x, oldV+_x);
    1263                 }
    1264             },
    1265 
    1266             h: {
    1267                 get: () => {return _h;},
    1268                 set: v => {
    1269                     oldV = _h;
    1270                     _h = v;
    1271                     if(obj.writeBoxY !== null) obj.writeBoxY(v+_y, oldV+_y);
    1272                 }
    1273             },
    1274 
    1275         });
    1276     }
    1277 
    1278     #box = new Box();
    1279     #image = null;
    1280     #isLoadImage = false;
    1281     #__bindObj = {
    1282         writeBoxX: null,
    1283         writeBoxY: null,
    1284     }
    1285 
    1286     get box(){return this.#box;}
    1287     get image(){return this.#image;}
    1288     get isScale(){return this.#image.width !== this.box.w || this.#image.height !== this.box.h;}
    1289     get width(){return this.#image !== null ? this.#image.width : 0;}
    1290     get height(){return this.#image !== null ? this.#image.height : 0;}
    1291     get isLoadImage(){return this.#isLoadImage;}
    1292     get __bindObj(){return this.#__bindObj;}
    1293 
    1294     get x(){return this.box.x;}
    1295     get y(){return this.box.y;}
    1296     set x(v){this.box.x = v;}
    1297     set y(v){this.box.y = v;}
    1298     
    1299     constructor(image){
    1300         this.opacity = 1;
    1301         this.visible = true;
    1302         this.rotate = null;
    1303         this.scale = null;
    1304 
    1305         //以下属性不建议直接修改
    1306         this.overlap = null;
    1307         this.index = -1;
    1308 
    1309         CanvasImage.bindBox(this.#__bindObj, this.#box);
    1310         this.setImage(image);
    1311     }
    1312 
    1313     pos(x, y){
    1314         if(UTILS.isNumber(x)){
    1315             this.box.x = x;
    1316             this.box.y = y;
    1317         }
    1318         else if(UTILS.isObject(x)){
    1319             this.box.x = x.x;
    1320             this.box.y = x.y;
    1321         }
    1322         return this;
    1323     }
    1324 
    1325     load(src, onload){
    1326         this.#isLoadImage = true;
    1327         const image = new Image();
    1328         
    1329         image.onload = () => {
    1330             this.setImage(image);
    1331             this.#isLoadImage = false;
    1332 
    1333             if(Array.isArray(this.loadImage_cis)){
    1334                 this.loadImage_cis.forEach(ci => ci.setImage(image));
    1335                 delete this.loadImage_cis;
    1336             }
    1337 
    1338             if(typeof onload === "function") onload(image);
    1339             else if(CanvasImageRender.prototype.isPrototypeOf(onload)){
    1340                 if(onload.domElement.parentElement !== null) onload.redraw();
    1341                 else onload.render();
    1342             }
    1343         }
    1344 
    1345         image.src = src;
    1346         return this;
    1347     }
    1348 
    1349     setImage(image){
    1350         if(CanvasImageRender.isCanvasImage(image)){
    1351             this.box.size(image.width, image.height);
    1352             this.#image = image;
    1353         }
    1354         else if(CanvasImage.prototype.isPrototypeOf(image)){
    1355             if(image.isLoadImage){
    1356                 if(Array.isArray(image.loadImage_cis)) image.loadImage_cis.push(this);
    1357                 else image.loadImage_cis = [this];
    1358             }
    1359             else this.setImage(image.image);
    1360         }
    1361         else{
    1362             this.box.size(0, 0);
    1363             this.#image = null;
    1364         }
    1365         return this;
    1366     }
    1367 
    1368 }
    1369 
    1370 
    1371 
    1372 
    1373 /* CanvasImages
    1374 
    1375 */
    1376 class CanvasImages extends CanvasImage{
    1377 
    1378     #i = -1;
    1379     get cursor(){return this.#i;}
    1380     set cursor(i){
    1381         super.setImage(this.images[i]);
    1382         this.#i = this.image !== null ? i : -1;
    1383     }
    1384 
    1385     constructor(images = []){
    1386         super(images[0]);
    1387         this.images = images;
    1388         if(this.image !== null) this.#i = 0;
    1389     }
    1390 
    1391     setImage(image){
    1392         super.setImage(image);
    1393         if(this.image !== null){
    1394             const i = this.images.indexOf(this.image);
    1395             if(i === -1){
    1396                 this.#i = this.images.length;
    1397                 this.images.push(this.image);
    1398             }
    1399             else this.#i = i;
    1400         }
    1401         return this;
    1402     }
    1403 
    1404     next(){
    1405         const len = this.images.length - 1;
    1406         if(len !== -1){
    1407             if(this.#i < len) this.#i++;
    1408             else this.#i = 0;
    1409             super.setImage(this.images[this.#i]);
    1410         }
    1411     }
    1412 
    1413     loads(srcs, onDone, onUpdate){
    1414         onUpdate = typeof onUpdate === "function" ? onUpdate : null;
    1415         var i = 0, c = srcs.length, img = null, _i = this.images.length;
    1416 
    1417         const len = srcs.length, 
    1418         func = ()=>{
    1419             i++; if(onUpdate !== null) onUpdate(this.images, _i);
    1420             if(i === c && typeof onDone === "function"){
    1421                 this.cursor = 0;
    1422                 onDone(this.images, _i, srcs);
    1423             }
    1424             else _i++;
    1425         }
    1426 
    1427         for(let k = 0, ty = ""; k < len; k++){
    1428             ty = typeof srcs[k];
    1429             if(ty === "string" || ty === "object"){
    1430                 ty = ty === "string" ? srcs[k] : srcs[k].src;
    1431                 if(ty !== "" && typeof ty === "string"){
    1432                     img = new Image();
    1433                     img.onload = func;
    1434                     this.images.push(img);
    1435                     img.src = ty;
    1436                 }
    1437                 else c--;
    1438             }
    1439         }
    1440 
    1441         return this;
    1442     }
    1443 
    1444 }
    1445 
    1446 
    1447 
    1448 
    1449 /* CanvasAnimateExtend
    1450 
    1451 demo:
    1452     //CanvasImageRender
    1453     const cir = new CanvasImageRender({ WORLD.width, height: WORLD.height});
    1454     cir.domElement.style = `
    1455         position: absolute;
    1456         z-index: 9999;
    1457         background: rgb(127,127,127);
    1458     `;
    1459 
    1460     //values
    1461     cir.add(new CanvasImageExtend())
    1462     .pos(59, 180)
    1463     .load("view/examples/img/test.png", cir);
    1464 
    1465     cir.add(new CanvasImageExtend())
    1466     .pos(59, 180)
    1467     .text("value", "red")
    1468     .rect().stroke()
    1469 
    1470 */
    1471 class CanvasImageExtend extends CanvasImage{
    1472 
    1473     constructor(canvas){
    1474         super(canvas);
    1475     }
    1476 
    1477     shear(canvas, dx = 0, dy = 0){
    1478         if(CanvasImageRender.isCanvas(canvas) === false){
    1479             canvas = document.createElement("canvas");
    1480             canvas.width = this.width;
    1481             canvas.height = this.height;
    1482         }
    1483 
    1484         canvas.getContext("2d").drawImage(this.image, dx, dy); 
    1485 
    1486         return canvas;
    1487     }
    1488 
    1489     setImage(image){
    1490         if(CanvasImageRender.isCanvas(image)){ //image 如果是画布
    1491             super.setImage(image);
    1492             this.context = CanvasImageRender.getContext(image);
    1493             this.size(image.width, image.height);
    1494         }
    1495         else{
    1496             if(CanvasImageRender.isCanvasImage(image)){ //image 如果是图像
    1497                 this.context = CanvasImageRender.getContext();
    1498                 super.setImage(this.context.canvas);
    1499                 this.size(image.width, image.height);
    1500                 this.context.drawImage(image, 0, 0);
    1501             }else{ //image 如果是其它对象
    1502                 if(image) super.setImage(image);
    1503                 else{
    1504                     this.context = CanvasImageRender.getContext();
    1505                     super.setImage(this.context.canvas);
    1506                     this.size(this.width, this.height);
    1507                 }
    1508             }
    1509         }
    1510         
    1511         return this;
    1512     }
    1513 
    1514     clear(){
    1515         this.context.clearRect(0, 0, this.box.w, this.box.h);
    1516         return this;
    1517     }
    1518 
    1519     size(w, h){
    1520         this.box.size(w, h);
    1521         this.image.width = w;
    1522         this.image.height = h;
    1523         CanvasImageRender.setDefaultStyles(this.context);
    1524         this.fontSize = parseFloat(CanvasImageRender.defaultStyles.font);
    1525         this.fillStyle = CanvasImageRender.defaultStyles.fillStyle;
    1526         this.strokeStyle = CanvasImageRender.defaultStyles.strokeStyle;
    1527         this.shadowColor = CanvasImageRender.defaultStyles.shadowColor
    1528         return this;
    1529     }
    1530 
    1531     shadow(shadowColor = "rgba(0,0,0,0)", shadowBlur, shadowOffsetX, shadowOffsetY){
    1532         const con = this.context;
    1533         if(typeof shadowColor === "string" && this.shadowColor !== shadowColor) this.shadowColor = con.shadowColor = shadowColor;
    1534         if(con.shadowBlur !== shadowBlur && typeof shadowBlur === "number") con.shadowBlur = shadowBlur;
    1535         if(con.shadowOffsetX !== shadowOffsetX && typeof shadowOffsetX === "number") con.shadowOffsetX = shadowOffsetX;
    1536         if(con.shadowOffsetY !== shadowOffsetY && typeof shadowOffsetY === "number") con.shadowOffsetY = shadowOffsetY;
    1537         return this;
    1538     }
    1539 
    1540     line(x, y, x1, y1){
    1541         this.context.beginPath();
    1542         this.context.moveTo(x, y);
    1543         this.context.lineTo(x1, y1);
    1544         return this;
    1545     }
    1546 
    1547     path(arr, close = false){
    1548         const con = this.context;
    1549         con.beginPath();
    1550         con.moveTo(arr[0], arr[1]);
    1551         for(let k = 2, len = arr.length; k < len; k+=2) con.lineTo(arr[k], arr[k+1]);
    1552         if(close === true) con.closePath();
    1553         return this;
    1554     }
    1555 
    1556     rect(r){
    1557         const con = this.context, s = con.lineWidth;
    1558         var x = s / 2,
    1559             y = s / 2,
    1560             w = this.box.w - s,
    1561             h = this.box.h - s;
    1562 
    1563         if(UTILS.isNumber(r) === false || r <= 0){
    1564             return con.rect(x, y, w, h);
    1565         }
    1566 
    1567         const _x = x + r, 
    1568         _y = y + r, 
    1569         mx = x + w, 
    1570         my = y + h, 
    1571         _mx = mx - r, 
    1572         _my = my - r;
    1573         
    1574         //
    1575         con.moveTo(_x, y);
    1576         con.lineTo(_mx, y);
    1577         con.arcTo(mx, y, mx, _y, r);
    1578 
    1579         //
    1580         con.lineTo(mx, _y);
    1581         con.lineTo(mx, _my);
    1582         con.arcTo(mx, my, _x, my, r);
    1583 
    1584         //
    1585         con.lineTo(_x, my);
    1586         con.lineTo(_mx, my);
    1587         con.arcTo(x, my, x, _my, r);
    1588 
    1589         //
    1590         con.lineTo(x, _y);
    1591         con.lineTo(x, _my);
    1592         con.arcTo(x, y, _x, y, r);
    1593 
    1594         return this;
    1595     }
    1596 
    1597     stroke(color, lineWidth){
    1598         if(typeof color === "string" && this.strokeStyle !== color) this.strokeStyle = this.context.strokeStyle = color;
    1599         if(UTILS.isNumber(lineWidth) && this.context.lineWidth !== lineWidth) this.context.lineWidth = lineWidth;
    1600         this.context.stroke();
    1601         return this;
    1602     }
    1603 
    1604     fill(color){
    1605         if(typeof color === "string" && this.fillStyle !== color) this.fillStyle = this.context.fillStyle = color;
    1606         this.context.fill();
    1607         return this;
    1608     }
    1609 
    1610     text(value, color, fontSize = 12, x = -1, y = -1){
    1611         if(this.fontSize !== fontSize){
    1612             this.fontSize = fontSize;
    1613             this.context.font = fontSize+"px SimSun, Songti SC";
    1614         }
    1615 
    1616         const textWidth = this.context.measureText(value).width;
    1617         if(textWidth > this.box.w || this.fontSize > this.box.h){
    1618             this.size(textWidth+4, this.fontSize+4);
    1619             this.fontSize = fontSize;
    1620             this.context.font = fontSize+"px SimSun, Songti SC";
    1621         }
    1622 
    1623         if(x === -1) x = (this.box.w - textWidth) / 2;
    1624         if(y === -1) y = (this.box.h - this.fontSize) / 2;
    1625 
    1626         if(typeof color === "string" && this.fillStyle !== color) this.fillStyle = this.context.fillStyle = color;
    1627         this.context.fillText(value, x, y);
    1628 
    1629         return this;
    1630     }
    1631 
    1632 }
    1633 
    1634 
    1635 
    1636 export {
    1637     CanvasImageRender,
    1638     CanvasImage, 
    1639     CanvasImages,
    1640     CanvasImageExtend,
    1641 }
    CanvasImageRender

    初始化  canvas:

     1 //CanvasImageRender
     2     const cir = new CanvasImageRender({
     3          window.innerWidth,   //canvas 的宽高
     4         height: window.innerHeight,
     5     
     6         scroll: true,                 //是否启用滚动轴; 默认 false
     7         scrollSize: 2,                //滚动轴的宽或高; 默认 6;
     8         scrollVisible: "auto",        //滚动轴的显示类型; 可能值 "auto" || "visible" || 默认"";
     9         scrollEventType: "touch",   //可能的值: 默认"", "default", "touch";
    10 
    11         inertia: true,                //是否启用移动端滚动轴的惯性; 默认 false
    12         inertiaLife: 0.05,            //移动端滚动轴惯性生命(阻力强度); 0 - 1; 默认 0.04;
    13     });
    14 
    15     //.domElement 是一个 canvas
    16     cir.domElement.style = `
    17         position: absolute;
    18         z-index: 9999;
    19         background: rgb(127,127,127);
    20     `;

    随机创建  CanvasImageRender 的成员:

     1     //这个用于生成图像共下面的for使用
     2     const cie = new CanvasImageExtend()
     3     .size(59, 59) //位置
     4     .rect(4) //定义圆角矩形路径
     5     .fill("rgba(0,0,150,0.2)") //矩形背景
     6     .text("value", "#fff", 16) //文字
     7     .stroke("rgba(0,0,255,0.6)"); //矩形边框
     8 
     9     for(let k = 0, ca, pi2 = Math.PI*2; k < 1000; k++){
    10         //cie.clear().text(String(k)) //更新文字
    11         //让 ca.image 等于 cie.iamge 并添加至 cir.list 数组 (画布像这样渲染文字性能会很棒!)
    12         cir.list[k] = ca = new CanvasImage(cie);
    13         
    14         //随机位置
    15         ca.pos(UTILS.random(0, 10000), UTILS.random(0, 10000));
    16 
    17         //随机旋转
    18         ca.rotate = new Rotate(ca.width/2, ca.height/2, UTILS.random(0, pi2));
    19 
    20         //随机缩放
    21         if(k % 2 === 0) ca.box.size(UTILS.random(1, ca.width), UTILS.random(1, ca.height));
    22     }
    23     
    24     //如果用 cir.add() 方法加入至列表就不需要在调用 cir.initList() 方法, 但是 cir.add() 方法不适合一次加很多个;
    25     //cir.remove() 也是同理
    26     cir.initList()
    27     .render(); //把 canvas 添加至dom树

    最终效果图:

  • 相关阅读:
    牛客练习赛64 D-宝石装箱(容斥定律,背包)
    CF-GYM-[2019 USP Try-outs] 部分题解
    [Codeforces Round #642 (Div. 3)] ABCDEF题解
    [NCD 2019] G. Ali and the Breakfast (解析几何)
    [AtCoder Beginner Contest 165] E
    [Educational Codeforces Round 86 (Rated for Div. 2)] E. Placing Rooks (组合数学,容斥定律)
    [AtCoder Beginner Contest 164] -E
    牛客算法周周练3 C -小雨坐地铁(分层最短路)
    HDU 5726 GCD (RMQ + 二分)
    Codeforces Round #362 (Div. 2) C. Lorenzo Von Matterhorn (类似LCA)
  • 原文地址:https://www.cnblogs.com/weihexinCode/p/16667275.html
Copyright © 2020-2023  润新知