原生 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({width: 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         width: 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({width: 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({width: 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         width: 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树

 

最终效果图:

 

 

 

 

posted @ 2022-09-07 21:05  鸡儿er  阅读(196)  评论(0编辑  收藏  举报