主頁 > 企業開發 > 可視化除錯某個js物件的屬性UI插件 class HTUI

可視化除錯某個js物件的屬性UI插件 class HTUI

2023-03-06 07:52:39 企業開發

 

依賴的類:

   1 "use strict";
   2 
   3 var __emptyPoint = 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     get time(){
 157         return Date.now();
 158     },
 159 
 160     get isMobile(){
 161         return /Android|webOS|iPhone|iPod|BlackBerry|SymbianOS|Windows Phone|iPad/i.test(navigator.userAgent);
 162     },
 163 
 164     get emptyPoint(){
 165         if(__emptyPoint === null) __emptyPoint = new Point();
 166         return __emptyPoint;
 167     },
 168 
 169     get emptyPointA(){
 170         if(__emptyPointA === null) __emptyPointA = new Point();
 171         return __emptyPointA;
 172     },
 173 
 174     get colorTable(){
 175         return ColorRefTable;
 176     },
 177     
 178     emptyArray(arr){
 179         return !Array.isArray(arr) || arr.length === 0;
 180     },
 181 
 182     isObject(obj){
 183         
 184         return obj !== null && typeof obj === "object" && Array.isArray(obj) === false;
 185         
 186     },
 187     
 188     isNumber(num){
 189 
 190         return typeof num === "number" && isNaN(num) === false;
 191 
 192     },
 193 
 194     //獲取最后一個點后面的字符(不包含點)
 195     getExtension(fileName){
 196         /* let type = "", str = fileName.split('').reverse().join('');
 197         for(let k = 0, len = str.length; k < len; k++){
 198             if(str[k] === ".") break;
 199             type += str[k];
 200         }
 201         return type.split('').reverse().join(''); */
 202         return fileName.substring(fileName.lastIndexOf(".")+1);
 203     },
 204     get getFileType(){
 205         console.warn("現在用 getExtension 替代 getFileType");
 206         return this.getExtension;
 207     },
 208 
 209     getFileName(fileName){
 210         return fileName.substring(0, fileName.lastIndexOf("."));
 211     },
 212 
 213     //洗掉 string 所有的空格
 214     deleteSpaceAll(str){
 215         const len = str.length;
 216         var result = '';
 217         for(let i = 0; i < len; i++){
 218             if(str[i] !== ' ') result += str[i]
 219         }
 220 
 221         return result
 222     },
 223 
 224     //str 是否全是中文
 225     isChains(str){
 226         return !(/[^\u4E00-\u9FA5]/.test(str));
 227     },
 228 
 229     //洗掉 string 兩邊空格
 230     removeSpaceSides(string){
 231 
 232         return string.replace(/(^\s*)|(\s*$)/g, "");
 233 
 234     },
 235 
 236     //var str = "abcd[123]efg"; UTILS.replaceText([..."a{123}b"], "{", "}", (str: "123") => {return "-"}) //["a", "-", "b"]
 237     replaceText(strs, s, e, f){
 238         var si = -1, str = "";
 239         for(let i = 0; i < strs.length; i++){
 240             if(typeof strs[i] !== "string"){
 241                 si = -1;
 242                 str = "";
 243                 continue;
 244             }
 245     
 246             if(si === -1){
 247                 if(strs[i] === s) si = i;
 248                 continue;
 249             }
 250     
 251             if(strs[i] === e){
 252                 strs[i] = f(str);
 253                 i = i-si;
 254                 strs.splice(si, i <= 0 ? 1 : i);
 255                 return this.replaceText(strs, s, e, f);
 256             }
 257             
 258             str += strs[i];
 259         }
 260     
 261         return strs;
 262     },
 263 
 264     //回傳 num 與 num1 之間的亂數
 265     random(num, num1){
 266         
 267         if(num < num1) return Math.random() * (num1 - num) + num;
 268 
 269         else if(num > num1) return Math.random() * (num - num1) + num1;
 270 
 271         else return num;
 272         
 273     },
 274 
 275     getByteStr(str = ""){
 276         var byte = 0;
 277         for(let i = 0; i < str.length; i++){
 278             if(str.charCodeAt(i) > 255){
 279                 byte += 2;
 280             } else {
 281                 byte++;
 282             }
 283         }
 284         return byte;
 285     },
 286 
 287     //生成 UUID
 288     generateUUID: function (){
 289         const _lut = [];
 290     
 291         for ( let i = 0; i < 256; i ++ ) {
 292     
 293             _lut[ i ] = ( i < 16 ? '0' : '' ) + ( i ).toString( 16 );
 294     
 295         }
 296     
 297         return function (){
 298             const d0 = Math.random() * 0xffffffff | 0;
 299             const d1 = Math.random() * 0xffffffff | 0;
 300             const d2 = Math.random() * 0xffffffff | 0;
 301             const d3 = Math.random() * 0xffffffff | 0;
 302             const uuid = _lut[ d0 & 0xff ] + _lut[ d0 >> 8 & 0xff ] + _lut[ d0 >> 16 & 0xff ] + _lut[ d0 >> 24 & 0xff ] + '-' +
 303             _lut[ d1 & 0xff ] + _lut[ d1 >> 8 & 0xff ] + '-' + _lut[ d1 >> 16 & 0x0f | 0x40 ] + _lut[ d1 >> 24 & 0xff ] + '-' +
 304             _lut[ d2 & 0x3f | 0x80 ] + _lut[ d2 >> 8 & 0xff ] + '-' + _lut[ d2 >> 16 & 0xff ] + _lut[ d2 >> 24 & 0xff ] +
 305             _lut[ d3 & 0xff ] + _lut[ d3 >> 8 & 0xff ] + _lut[ d3 >> 16 & 0xff ] + _lut[ d3 >> 24 & 0xff ];
 306     
 307             return uuid.toLowerCase(); //toLowerCase() 這里展平連接的字串以節省堆記憶體空間
 308         }
 309     }(),
 310 
 311     //歐幾里得距離(兩點的直線距離)
 312     distance(x, y, x1, y1){
 313         
 314         return Math.sqrt(Math.pow(x - x1, 2) + Math.pow(y - y1, 2));
 315 
 316     },
 317 
 318     getSameScale(oldSize, newSize){
 319         if(oldSize.width < oldSize.height && newSize.width < newSize.height){
 320             return newSize.width / oldSize.width;
 321         }
 322         if(oldSize.width > oldSize.height && newSize.width > newSize.height){
 323             return newSize.height / oldSize.height;
 324         }
 325         if(oldSize.width / newSize.width < oldSize.height / newSize.height){
 326             return newSize.height / oldSize.height;
 327         }
 328         const aspect = oldSize.width / oldSize.height;
 329         return aspect < 1 ? aspect * newSize.width / oldSize.width : newSize.width / oldSize.width;
 330     },
 331 
 332     /* 把 target 以相等的比例縮放至 result 大小
 333         target, result: Object{width, height}; //也可以是img元素
 334     */
 335     setSizeToSameScale(target, result = {width: 100, height: 100}){
 336         const scale = this.getSameScale(target, result);
 337         result.width = target.width * scale;
 338         result.height = target.height * scale;
 339         return result;
 340     },
 341 
 342     //小數保留 count 位
 343     floatKeep(float, count = 3){
 344         count = Math.pow(10, count);
 345         return Math.ceil(float * count) / count;
 346     },
 347 
 348 }
 349 
 350 
 351 
 352 
 353 /* AnimateLoop 影片回圈
 354 parameter:
 355     loopStep: Number; //默認 1000 / 60 (每秒運行60次)
 356     onupdate: Function(); //更新回呼, 默認 null
 357     onUpdateFPS: Function(fps); //如果定義此引數就在每幀計算并傳遞fps值, 數值越高越好, 默認 null
 358 
 359 attributes:
 360     onupdate: Func();
 361 
 362     //只讀:
 363     delta: Number;    //延遲
 364     running: Bool;     //是否正在運行
 365 
 366 method:
 367     play(onupdate: Func): this; //onupdate 可選 (如果影片回圈正在運行那么此方法什么都不會做)
 368     stop(): this;
 369     update(): undefined; //使用前必須已定義.onupdate 屬性 (如果影片回圈正在運行那么此方法什么都不會做)
 370 
 371 demo: 
 372     const animateLoop = new AnimateLoop(
 373         () => console.log(animateLoop.delta), 
 374         fps => console.log(fps)
 375     );
 376     animateLoop.play();
 377 */
 378 class AnimateLoop{
 379 
 380     #nowTime = 0;
 381     #oldTime = 0;
 382     #id = -1;
 383 
 384     get running(){
 385         return this.#id !== -1;
 386     }
 387 
 388     get delta(){
 389         return this.#nowTime - this.#oldTime;
 390     }
 391     
 392     constructor(onupdate = null, onUpdateFPS = null, loopStep = 1000 / 60){
 393         if(typeof onUpdateFPS !== "function"){
 394             const animate = () => {
 395                 this.#nowTime = UTILS.time;
 396                 this.#id = requestAnimationFrame(animate);
 397                 if(this.#nowTime - this.#oldTime >= loopStep){
 398                     this.onupdate();
 399                     this.#oldTime = this.#nowTime;
 400                 }
 401             }
 402 
 403             this._animate = animate;
 404         } else {
 405             //更新fps
 406             let old = UTILS.time, frames = 0;
 407             const updateFPS = () => {
 408                 frames++;
 409                 if (this.#nowTime >= old + 1000){
 410                     onUpdateFPS(Math.floor((frames * 1000) / (this.#nowTime - old)));
 411                     old = this.#nowTime;
 412                     frames = 0;
 413                 }
 414             },
 415 
 416             //影片回圈
 417             animate = () => {
 418                 this.#nowTime = UTILS.time;
 419                 this.#id = requestAnimationFrame(animate);
 420                 updateFPS();
 421                 if(this.#nowTime - this.#oldTime >= loopStep){
 422                     this.onupdate();
 423                     this.#oldTime = this.#nowTime;
 424                 }
 425             }
 426 
 427             this._animate = animate;
 428         }
 429         
 430         this.onupdate = onupdate;
 431     }
 432 
 433     stop(){
 434         if(this.#id !== -1){
 435             cancelAnimationFrame(this.#id);
 436             this.#id = -1;
 437             this.#oldTime = this.#nowTime;
 438         }
 439         return this;
 440     }
 441 
 442     play(onupdate){
 443         if(typeof onupdate === "function") this.onupdate = onupdate;
 444         if(this.onupdate !== null && this.#id === -1){
 445             this.#id = requestAnimationFrame(this._animate);
 446             this.#oldTime = UTILS.time;
 447         }
 448         return this;
 449     }
 450 
 451     update(){
 452         if(this.#id === -1){
 453             this.onupdate();
 454         }
 455     }
 456 
 457 }
 458 
 459 
 460 
 461 
 462 /* TweenCache
 463 parameter:
 464     origin, end: Object{Number...}, 
 465     time: Number, //origin 到 end 花費的毫秒時間
 466     onend: Function //
 467 
 468 demo:
 469     const tweenCache = new TweenCache({x:-50}, {x:100}, null, 3000);
 470     const animateLoop = new AnimateLoop(() => tweenCache.update());
 471 
 472     tweenCache.onend = () => {
 473         animateLoop.stop();
 474         tweenCache.reset();
 475     }
 476 
 477     animateLoop.play();
 478     tweenCache.start();
 479 */
 480 class TweenCache{
 481 
 482     #t = 0;
 483     #v = {};
 484     #o = {};
 485     
 486     constructor(origin, end, time, onend){
 487         this.origin = origin;
 488         this.end = end;
 489         this.time = time;
 490         this.onend = onend;
 491     }
 492 
 493     start(){
 494         this.#t = UTILS.time;
 495         for(let v in this.origin){
 496             this.#v[v] = this.end[v] - this.origin[v];
 497             this.#o[v] = this.origin[v];
 498         }
 499     }
 500 
 501     update(){
 502         var ted = UTILS.time - this.#t;
 503 
 504         if(ted < this.time){
 505 
 506             ted = ted / this.time; 
 507             for(let n in this.origin) this.origin[n] = ted * this.#v[n] + this.#o[n];
 508 
 509         } else {
 510         
 511             for(ted in this.origin) this.origin[ted] = this.end[ted];
 512             if(typeof this.onend === "function") this.onend();
 513 
 514         }
 515     }
 516     
 517 }
 518 
 519 
 520 
 521 
 522 /* TweenTarget
 523 parameter:    
 524     v1 = {x: 0}, 
 525     v2 = {x: 100}, 
 526     distance = 1,    //每次移動的距離
 527     onend = null    //
 528 
 529 attribute:
 530     v1: Object;             //起點
 531     v2: Object;             //終點
 532     onend: Function;         //
 533 
 534 method:
 535     update(): undefined;                        //一般在影片回圈里執行此方法
 536     updateAxis(): undefined;                     //更新v1至v2的方向軸 (初始化時構造器自動呼叫一次)
 537     setDistance(distance: Number): undefined;     //設定每次移動的距離 (初始化時構造器自動呼叫一次)
 538 */
 539 class TweenTarget{
 540 
 541     #distance = 1;
 542     #distancePow2 = 1;
 543     #axis = {};
 544     get axis(){return this.#axis;}
 545     get distance(){return this.#distance;}
 546 
 547     constructor(v1 = {x: 0}, v2 = {x: 100}, distance, onend = null){
 548         this.v1 = v1;
 549         this.v2 = v2;
 550         this.onend = onend;
 551         
 552         this.setDistance(distance);
 553         this.updateAxis();
 554     }
 555 
 556     setDistance(v = 1){
 557         this.#distance = v;
 558         this.#distancePow2 = Math.pow(v, 2);
 559     }
 560 
 561     updateAxis(){
 562         var n, v, len = 0;
 563         
 564         for(n in this.v1){
 565             v = this.v2[n] - this.v1[n];
 566             len += v * v;
 567             this.#axis[n] = v;
 568         }
 569 
 570         len = Math.sqrt(len);
 571 
 572         if(len !== 0){
 573             for(n in this.v1) this.#axis[n] *= 1 / len;
 574         }
 575     }
 576 
 577     update(){
 578         var n, len = 0;
 579 
 580         for(n in this.v1){
 581             len += Math.pow(this.v1[n] - this.v2[n], 2);
 582         }
 583         
 584         if(len > this.#distancePow2){
 585 
 586             for(n in this.v1){
 587                 this.v1[n] += this.#axis[n] * this.#distance;
 588             }
 589             
 590         }
 591 
 592         else{
 593 
 594             for(n in this.v1){
 595                 this.v1[n] = this.v2[n];
 596             }
 597 
 598             if(this.onend) this.onend();
 599 
 600         }
 601     }
 602 
 603 }
 604 
 605 
 606 
 607 
 608 /* SecurityDoor 安檢門
 609 作用: 有時候我們要給一個功能加一個啟禁用鎖, 就比如用一個.enable屬性表示此功能是否啟用, 
 610 a, b是兩個使用此功能的人; a 需要把此功能禁用(.enable = false), 
 611 過了一會b也要禁用此功能(.enable = false), 又過了一會a又要在次啟用此功能,
 612 此時a讓此功能啟用了(.enable = true), 很顯然這違背了b, 對于b來說此功能還在禁用狀態,
 613 實際并非如b所想, 所以當多個人使用此功能時一個.enable滿足不了它們;
 614 或許還有其它解決方法就比如讓所有使用此功能的人(a,b)加一個屬性表示自己是否可以使用此功能,
 615 但我更希望用一個專門的陣列去裝載它們, 而不是在某個使用者身上都添加一個屬性;
 616 
 617 parameter:
 618     list: Array[];
 619 
 620 attribute:
 621     list: Array; //私有, 默認 null
 622     empty: Bool; //只讀, 串列是否為空
 623 
 624 method:
 625     clear()
 626     equals(v)
 627     add(v)
 628     remove(v)
 629 */
 630 class SecurityDoor{
 631 
 632     #list = null;
 633     
 634     get empty(){
 635         return this.#list === null;
 636     }
 637 
 638     constructor(list, onchange){
 639         if(UTILS.emptyArray(list) === false) this.#list = list;
 640         this._onchange = typeof onchange === "function" ? onchange : null;
 641     }
 642 
 643     add(sign){
 644         if(this.#list !== null){
 645             if(this.#list.includes(sign) === false) this.#list.push(sign);
 646         }
 647         else{
 648             this.#list = [sign];
 649             if(this._onchange !== null) this._onchange(false);
 650         }
 651     }
 652     
 653     clear(){
 654         this.#list = null;
 655     }
 656 
 657     equals(sign){
 658         return this.#list !== null && this.#list.includes(sign);
 659     }
 660 
 661     remove(sign){
 662         if(this.#list !== null){
 663             sign = this.#list.indexOf(sign);
 664             if(sign !== -1){
 665                 if(this.#list.length > 1) this.#list.splice(sign, 1);
 666                 else{
 667                     this.#list = null;
 668                     if(this._onchange !== null) this._onchange(true);
 669                 }
 670             }
 671         }
 672     }
 673 
 674 }
 675 
 676 
 677 
 678 
 679 /* RunningList
 680 
 681 */
 682 class RunningList{
 683 
 684     #runName = "";
 685     #running = false;
 686     #list = [];
 687     #disabls = [];
 688     get list(){
 689         return this.#list;
 690     }
 691 
 692     constructor(runName = 'update'){
 693         this.#runName = runName;
 694     }
 695 
 696     clear(){
 697         this.#list.length = 0;
 698         this.#disabls.length = 0;
 699     }
 700 
 701     add(v){
 702         if(this.#list.includes(v) === false) this.#list.push(v);
 703         else{
 704             const i = this.#disabls.indexOf(v);
 705             if(i !== -1) this.#disabls.splice(i, 1);
 706         }
 707     }
 708 
 709     remove(v){
 710         if(this.#running) this.#disabls.push(v);
 711         else{
 712             const i = this.#list.indexOf(v);
 713             if(i !== -1) this.#list.splice(i, 1);
 714         }
 715     }
 716 
 717     update(){
 718         var len = this.#list.length;
 719         if(len === 0) return;
 720         
 721         var k;
 722         this.#running = true;
 723         if(this.#runName !== ''){
 724             for(k = 0; k < len; k++) this.#list[k][this.#runName]();
 725         }else{
 726             for(k = 0; k < len; k++) this.#list[k]();
 727         }
 728         this.#running = false;
 729 
 730         var i;
 731         len = this.#disabls.length;
 732         for(k = 0; k < len; k++){
 733             i = this.#list.indexOf(this.#disabls[k]);
 734             if(i !== -1) this.#list.splice(i, 1);
 735         }
 736         this.#disabls.length = 0;
 737     }
 738 
 739 }
 740 
 741 
 742 
 743 
 744 /** Ajax
 745 parameter:
 746     option = {
 747         url:        可選, 默認 ''
 748         method:        可選, post 或 get請求, 默認 post
 749         asy:        可選, 是否異步執行, 默認 true
 750         success:    可選, 成功回呼, 默認 null
 751         error:        可選, 超時或失敗呼叫, 默認 null
 752         change:        可選, 請求狀態改變時呼叫, 默認 null
 753         data:        可選, 如果定義則在初始化時自動執行.send(data)方法
 754     }
 755 
 756 demo:
 757     const data = https://www.cnblogs.com/weihexinCode/p/`email=${email}&password=${password}`,
 758 
 759     //默認 post 請求:
 760     ajax = new Ajax({
 761         url: './login',
 762         data: data,
 763         success: mes => console.log(mes),
 764     });
 765     
 766     //get 請求:
 767     ajax.method = "get";
 768     ajax.send(data);
 769 */
 770 class Ajax{
 771     
 772     constructor(option = {}){
 773         this.url = option.url || "";
 774         this.method = option.method || "post";
 775         this.asy = typeof option.asy === "boolean" ? option.asy : true;
 776         this.success = option.success || null;
 777         this.error = option.error || null;
 778         this.change = option.change || null;
 779 
 780         //init XML
 781         this.xhr = new XMLHttpRequest();
 782 
 783         this.xhr.onerror = this.xhr.ontimeout = event => {
 784             if(this.error !== null) this.error(event);
 785         }
 786 
 787         this.xhr.onreadystatechange = event => {
 788         
 789             if(event.target.readyState === 4 && event.target.status === 200){
 790 
 791                 if(this.success !== null) this.success(event.target.responseText, event);
 792                 
 793             }
 794 
 795             else if(this.change !== null) this.change(event);
 796 
 797         }
 798 
 799         if(option.data) this.send(option.data);
 800     }
 801 
 802     send(datahttps://www.cnblogs.com/weihexinCode/p/= ""){
 803         if(this.method === "get"){
 804             this.xhr.open(this.method, this.url+"?"+data, this.asy);
 805             this.xhr.send();
 806         }
 807         
 808         else if(this.method === "post"){
 809             this.xhr.open(this.method, this.url, this.asy);
 810             this.xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
 811             this.xhr.send(data);
 812         }
 813     }
 814     
 815 }
 816 
 817 
 818 
 819 
 820 /* IndexedDB 本地資料庫
 821 
 822 parameter:
 823     name: String;                //需要打開的資料庫名稱(如果不存在則會新建一個) 必須
 824     done: Function(IndexedDB);    //鏈接資料庫成功時的回呼 默認 null
 825     version: Number;             //資料庫版本(高版本資料庫將覆寫低版本的資料庫) 默認 1 
 826 
 827 attribute:
 828     database: IndexedDB;            //鏈接完成的資料庫物件
 829     transaction: IDBTransaction;    //事務管理(讀和寫)
 830     objectStore: IDBObjectStore;    //當前的事務
 831 
 832 method:
 833     set(data, key, callback)        //添加或更新
 834     get(key, callback)                //獲取
 835     delete(key, callback)            //洗掉
 836 
 837     traverse(callback)                //遍歷
 838     getAll(callback)                //獲取全部
 839     clear(callback)                    //清理所以資料
 840     close()                         //關閉資料庫鏈接
 841 
 842 readOnly:
 843 
 844 static:
 845     indexedDB: Object;
 846 
 847 demo:
 848     
 849     new IndexedDB('TEST', db => {
 850 
 851         conosle.log(db);
 852 
 853     });
 854 
 855 */
 856 class IndexedDB{
 857 
 858     static indexedDB = globalThis.indexedDB || globalThis.webkitIndexedDB || globalThis.mozIndexedDB || globalThis.msIndexedDB;
 859 
 860     get objectStore(){ //每個事務只能使用一次, 所以每次都需要重新創建
 861         return this.database.transaction(this.name, 'readwrite').objectStore(this.name);
 862     }
 863 
 864     constructor(name, done = null, version = 1){
 865 
 866         if(IndexedDB.indexedDB === undefined) return console.error("IndexedDB: 不支持IndexedDB");
 867         
 868         if(typeof name !== 'string') return console.warn('IndexedDB: 引數錯誤');
 869 
 870         this.name = name;
 871         this.database = null;
 872 
 873         const request = IndexedDB.indexedDB.open(name, version);
 874         
 875         request.onupgradeneeded = (e)=>{ //資料庫不存在 或 版本號不同時 觸發
 876             if(!this.database) this.database = e.target.result;
 877             if(this.database.objectStoreNames.contains(name) === false) this.database.createObjectStore(name);
 878         }
 879         
 880         request.onsuccess = (e)=>{
 881             this.database = e.target.result;
 882             if(typeof done === 'function') done(this);
 883         }
 884         
 885         request.onerror = (e)=>{
 886             console.error(e);
 887         }
 888         
 889     }
 890 
 891     close(){
 892 
 893         return this.database.close();
 894 
 895     }
 896 
 897     clear(callback){
 898         
 899         this.objectStore.clear().onsuccess = callback;
 900         
 901     }
 902 
 903     traverse(callback){
 904         
 905         this.objectStore.openCursor().onsuccess = callback;
 906 
 907     }
 908 
 909     set(data, key = 0, callback){
 910         
 911         this.objectStore.put(data, key).onsuccess = callback;
 912 
 913     }
 914     
 915     get(key = 0, callback){
 916 
 917         this.objectStore.get(key).onsuccess = callback;
 918         
 919     }
 920 
 921     del(key = 0, callback){
 922 
 923         this.objectStore.delete(key).onsuccess = callback;
 924 
 925     }
 926     
 927     getAll(callback){
 928 
 929         this.objectStore.getAll().onsuccess = callback;
 930 
 931     }
 932 
 933 }
 934 
 935 
 936 
 937 
 938 /* TreeStruct 樹結構基類
 939 
 940 attribute:
 941     parent: TreeStruct;
 942     children: Array[TreeStruct];
 943 
 944 method:
 945     appendChild(v: TreeStruct): v;         //v添加到自己的子集
 946     removeChild(v: TreeStruct): v;     //洗掉v, 前提v必須是自己的子集
 947     export(): Array[Object];    //TreeStruct 轉為 可匯出的結構, 包括其所有的后代
 948 
 949     getPath(v: TreeStruct): Array[TreeStruct];     //獲取自己到v的路徑
 950 
 951     traverse(callback: Function): undefined;  //迭代自己的每一個后代, 包括自己
 952         callback(value: TreeStruct); //如回傳 "continue" 則不在迭代其后代(不是結束迭代, 而是只結束當前節點的后代);
 953 
 954     traverseUp(callback): undefined; //向上遍歷每一個父, 包括自己
 955         callback(value: TreeStruct); //如回傳 "break" 立即停止遍歷;
 956 
 957 static:
 958     import(arr: Array[Object]): TreeStruct; //.export() 回傳的 arr 轉為 TreeStruct
 959 
 960 */
 961 class TreeStruct{
 962 
 963     static import(arr){
 964 
 965         //json = JSON.parse(json);
 966         const len = arr.length;
 967 
 968         for(let k = 0, v; k < len; k++){
 969             v = Object.assign(new TreeStruct(), arr[k]);
 970             v.parent = arr[arr[k].parent] || null;
 971             if(v.parent !== null) v.parent.appendChild(v);
 972             arr[k] = v;
 973         }
 974 
 975         return arr[0];
 976 
 977     }
 978 
 979     constructor(){
 980         this.parent = null;
 981         this.children = [];
 982     }
 983 
 984     getPath(v){
 985 
 986         var path;
 987 
 988         const pathA = [];
 989         this.traverseUp(tar => {
 990             if(v === tar){
 991                 path = pathA;
 992                 return "break";
 993             }
 994             pathA.push(tar);
 995         });
 996 
 997         if(path) return path;
 998 
 999         const pathB = [];
1000         v.traverseUp(tar => {
1001             if(this === tar){
1002                 path = pathB.reverse();
1003                 return "break";
1004             }
1005             else{
1006                 let i = pathA.indexOf(tar);
1007                 if(i !== -1){
1008                     pathA.splice(i);
1009                     pathA.push(tar);
1010                     path = pathA.concat(pathB.reverse());
1011                     return "break";
1012                 }
1013             }
1014             pathB.push(tar);
1015         });
1016 
1017         return path;
1018         
1019     }
1020 
1021     appendChild(v){
1022         v.parent = this;
1023         if(this.children.includes(v) === false) this.children.push(v);
1024         
1025         return v;
1026     }
1027 
1028     removeChild(v){
1029         const i = this.children.indexOf(v);
1030         if(i !== -1) this.children.splice(i, 1);
1031         v.parent = null;
1032 
1033         return v;
1034     }
1035 
1036     traverse(callback){
1037 
1038         if(callback(this) !== "continue"){
1039 
1040             for(let k = 0, len = this.children.length; k < len; k++){
1041 
1042                 this.children[k].traverse(callback);
1043     
1044             }
1045 
1046         }
1047 
1048     }
1049 
1050     traverseUp(callback){
1051 
1052         var par = this.parent;
1053 
1054         while(par !== null){
1055             if(callback(par) === "break") return;
1056             par = par.parent;
1057         }
1058 
1059     }
1060 
1061     export(){
1062 
1063         const result = [], arr = [];
1064         var obj = null;
1065 
1066         this.traverse(v => {
1067             obj = Object.assign({}, v);
1068             obj.parent = arr.indexOf(v.parent);
1069             delete obj.children;
1070             result.push(obj);
1071             arr.push(v);
1072         });
1073         
1074         return result; //JSON.stringify(result);
1075 
1076     }
1077 
1078 }
1079 
1080 
1081 
1082 
1083 /* Point
1084 parameter: 
1085     x = 0, y = 0;
1086 
1087 attribute
1088     x, y: Number;
1089 
1090 method:
1091     set(x, y): this;
1092     angle(origin): Number;
1093     copy(point): this;
1094     clone(): Point;
1095     distance(point): Number;            //獲取歐幾里得距離
1096     distanceMHD(point): Number;            //獲取曼哈頓距離
1097     distanceCompare(point): Number;        //獲取用于比較的距離(相對于.distance() 效率更高)
1098     equals(point): Bool;                //是否恒等
1099     reverse(): this;                    //取反值
1100     rotate(origin: Object{x,y}, angle): this;    //旋轉點
1101     normalize(): this;                    //歸一
1102     isClockwise(startPoint): Bool;        //startPoint 到自己是否是順時針旋轉
1103 */
1104 class Point{
1105 
1106     get isPoint(){return true;}
1107 
1108     constructor(x = 0, y = 0){
1109         this.x = x;
1110         this.y = y;
1111     }
1112 
1113     set(x = 0, y = 0){
1114         this.x = x;
1115         this.y = y;
1116 
1117         return this;
1118     }
1119 
1120     radian(origin){
1121 
1122         return Math.atan2(this.y - origin.y, this.x - origin.x);
1123 
1124     }
1125 
1126     copy(point){
1127         
1128         this.x = point.x;
1129         this.y = point.y;
1130         return this;
1131         //return Object.assign(this, point);
1132 
1133     }
1134     
1135     clone(){
1136 
1137         return new this.constructor().copy(this);
1138         //return Object.assign(new this.constructor(), this);
1139         
1140     }
1141 
1142     distance(point){
1143         
1144         return Math.sqrt(Math.pow(point.x - this.x, 2) + Math.pow(point.y - this.y, 2));
1145 
1146     }
1147 
1148     distanceMHD(point){
1149 
1150         return Math.abs(this.x - point.x) + Math.abs(this.y - point.y);
1151 
1152     }
1153 
1154     distanceCompare(point){
1155     
1156         return Math.pow(this.x - point.x, 2) + Math.pow(this.y - point.y, 2);
1157 
1158     }
1159 
1160     equals(point){
1161 
1162         return point.x === this.x && point.y === this.y;
1163 
1164     }
1165 
1166     reverse(){
1167         this.x = -this.x;
1168         this.y = -this.y;
1169 
1170         return this;
1171     }
1172 
1173     rotate(origin, radian){
1174         const c = Math.cos(radian), s = Math.sin(radian), 
1175         x = this.x - origin.x, y = this.y - origin.y;
1176 
1177         this.x = x * c - y * s + origin.x;
1178         this.y = x * s + y * c + origin.y;
1179 
1180         return this;
1181     }
1182 
1183     normalize(){
1184         const len = 1 / (Math.sqrt(this.x * this.x + this.y * this.y) || 1);
1185         this.x *= len;
1186         this.y *= len;
1187 
1188         return this;
1189     }
1190 
1191     isClockwise(startPoint){
1192 
1193         return this.x * startPoint.y - this.y * startPoint.x < 0;
1194 
1195     }
1196 
1197     floatKeep(count = 3){
1198         count = Math.pow(10, count);
1199         this.x = Math.ceil(this.x * count) / count;
1200         this.y = Math.ceil(this.y * count) / count;
1201     }
1202 
1203 /*     add(point){
1204         this.x += point.x;
1205         this.y += point.y;
1206         return this;
1207     }
1208 
1209     addScalar(v){
1210         this.x += v;
1211         this.y += v;
1212         return this;
1213     }
1214 
1215     sub(point){
1216         this.x -= point.x;
1217         this.y -= point.y;
1218         return this;
1219     }
1220 
1221     subScalar(v){
1222         this.x -= v;
1223         this.y -= v;
1224         return this;
1225     }
1226 
1227     multiply(point){
1228         this.x *= point.x;
1229         this.y *= point.y;
1230         return this;
1231     }
1232 
1233     multiplyScalar(v){
1234         this.x *= v;
1235         this.y *= v;
1236         return this;
1237     }
1238 
1239     divide(point){
1240         this.x /= point.x;
1241         this.y /= point.y;
1242         return this;
1243     }
1244     
1245     divideScalar(v){
1246         this.x /= v;
1247         this.y /= v;
1248         return this;
1249     } */
1250 
1251 }
1252 
1253 
1254 
1255 
1256 //BoundaryBox 邊界框
1257 class BoundaryBox{
1258     
1259     constructor(){
1260         this.x = 0;
1261         this.y = 0;
1262         this.mx = 0;
1263         this.my = 0;
1264     }
1265 
1266     setFromBox(box){
1267         this.x = box.x;
1268         this.y = box.y;
1269         this.mx = box.mx;
1270         this.my = box.my;
1271     }
1272 
1273     setFromPolygon(polygon){
1274         const len = polygon.path.length;
1275         let x = Infinity, y = Infinity, mx = -Infinity, my = -Infinity;
1276         for(let k = 0, v; k < len; k+=2){
1277             v = polygon.path[k];
1278             if(v < x) x = v;
1279             else if(v > mx) mx = v;
1280 
1281             v = polygon.path[k+1];
1282             if(v < y) y = v;
1283             else if(v > my) my = v;
1284         }
1285 
1286         this.x = x;
1287         this.y = y;
1288         this.mx = mx;
1289         this.my = my;
1290         return this;
1291     }
1292 
1293 }
1294 
1295 
1296 
1297 
1298 /* Line
1299 parameter: x, y, x1, y1: Number;
1300 attribute: x, y, x1, y1: Number;
1301 method:
1302     set(x, y, x1, y1): this;                    
1303     copy(line): this;
1304     clone(): Line;
1305     containsPoint(x, y): Bool;                             //點是否在線上
1306     intersectPoint(line: Line, point: Point): Point;    //如果不相交則回傳null, 否則回傳交點Point
1307     isIntersect(line): Bool;                             //this與line是否相交
1308 */
1309 class Line{
1310 
1311     constructor(x = 0, y = 0, x1 = 0, y1 = 0){
1312         this.x = x;
1313         this.y = y;
1314         this.x1 = x1;
1315         this.y1 = y1;
1316     }
1317 
1318     set(x = 0, y = 0, x1 = 0, y1 = 0){
1319         this.x = x;
1320         this.y = y;
1321         this.x1 = x1;
1322         this.y1 = y1;
1323         return this;
1324     }
1325 
1326     copy(line){
1327         this.x = line.x;
1328         this.y = line.y;
1329         this.x1 = line.x1;
1330         this.y1 = line.y1;
1331         return this;
1332         //return Object.assign(this, line);
1333     }
1334     
1335     clone(){
1336         return new this.constructor().copy(this);
1337         //return Object.assign(new this.constructor(), this);
1338     }
1339 
1340     containsPoint(x, y){
1341         return (x - this.x) * (x - this.x1) <= 0 && (y - this.y) * (y - this.y1) <= 0;
1342     }
1343 
1344     intersectPoint(line, point){
1345         //解線性方程組, 求線段交點
1346         //如果分母為0則平行或共線, 不相交
1347         var denominator = (this.y1 - this.y) * (line.x1 - line.x) - (this.x - this.x1) * (line.y - line.y1);
1348         if(denominator === 0) return null;
1349 
1350         //線段所在直線的交點坐標 (x , y)
1351         const x = ((this.x1 - this.x) * (line.x1 - line.x) * (line.y - this.y) 
1352         + (this.y1 - this.y) * (line.x1 - line.x) * this.x 
1353         - (line.y1 - line.y) * (this.x1 - this.x) * line.x) / denominator;
1354 
1355         const y = -((this.y1 - this.y) * (line.y1 - line.y) * (line.x - this.x) 
1356         + (this.x1 - this.x) * (line.y1 - line.y) * this.y 
1357         - (line.x1 - line.x) * (this.y1 - this.y) * line.y) / denominator;
1358 
1359         //判斷交點是否在兩條線段上
1360         if(this.containsPoint(x, y) && line.containsPoint(x, y)){
1361             point.x = x;
1362             point.y = y;
1363             return point;
1364         }
1365 
1366         return null;
1367     }
1368 
1369     isIntersect(line){
1370         //快速排斥:
1371         //兩個線段為對角線組成的矩形,如果這兩個矩形沒有重疊的部分,那么兩條線段是不可能出現重疊的
1372 
1373         //這里的確如此,這一步是判定兩矩形是否相交
1374         //1.線段ab的低點低于cd的最高點(可能重合)
1375         //2.cd的最左端小于ab的最右端(可能重合)
1376         //3.cd的最低點低于ab的最高點(加上條件1,兩線段在豎直方向上重合)
1377         //4.ab的最左端小于cd的最右端(加上條件2,兩直線在水平方向上重合)
1378         //綜上4個條件,兩條線段組成的矩形是重合的
1379         //特別要注意一個矩形含于另一個矩形之內的情況
1380         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;
1381 
1382         //跨立實驗:
1383         //如果兩條線段相交,那么必須跨立,就是以一條線段為標準,另一條線段的兩端點一定在這條線段的兩段
1384         //也就是說a b兩點在線段cd的兩端,c d兩點在線段ab的兩端
1385         var u=(line.x-this.x)*(this.y1-this.y)-(this.x1-this.x)*(line.y-this.y),
1386         v = (line.x1-this.x)*(this.y1-this.y)-(this.x1-this.x)*(line.y1-this.y),
1387         w = (this.x-line.x)*(line.y1-line.y)-(line.x1-line.x)*(this.y-line.y),
1388         z = (this.x1-line.x)*(line.y1-line.y)-(line.x1-line.x)*(this.y1-line.y);
1389         
1390         return u*v <= 0.00000001 && w*z <= 0.00000001;
1391     }
1392 
1393 }
1394 
1395 
1396 
1397 
1398 /* Box 矩形
1399 parameter: 
1400     x = 0, y = 0, w = 0, h = 0;
1401 
1402 attribute:
1403     x,y: Number; 位置
1404     w,h: Number; 大小
1405 
1406     只讀
1407     mx, my: Number; //
1408 
1409 method:
1410     set(x, y, w, h): this;
1411     pos(x, y): this; //設定位置
1412     size(w, h): this; //設定大小
1413     setFromRotate(rotate: Rotate): this;        //根據 rotate 旋轉自己
1414     setFromShapeRect(shapeRect): this;            //ShapeRect 轉為 Box (忽略 ShapeRect 的旋轉和縮放)
1415     setFromCircle(circle, inner: Bool): this;    //
1416     setFromPolygon(polygon, inner = true): this;//
1417     toArray(array: Array, index: Integer): this;
1418     copy(box): this;                             //復制
1419     clone(): Box;                                  //克隆
1420     center(box): this;                            //設定位置在box居中
1421     distance(x, y): Number;                     //左上角原點 與 x,y 的直線距離
1422     distanceFromPoint(x,y, isMax: Bool): Number;//回傳點 x,y 距自己四個角的 最大或最小(isMax) 距離
1423     isEmpty(): Boolean;                         //.w.h是否小于等于零
1424     maxX(): Number;                             //回傳 max x(this.x+this.w);
1425     maxY(): Number;                             //回傳 max y(this.y+this.h);
1426     expand(box): undefined;                     //擴容; 把box合并到this
1427     equals(box): Boolean;                         //this與box是否恒等
1428     intersectsBox(box): Boolean;                 //box與this是否相交(box在this內部也會回傳true)
1429     containsPoint(x, y): Boolean;                 //x,y點是否在this內
1430     containsBox(box): Boolean;                    //box是否在this內(只是相交回傳fasle)
1431     computeOverflow(b: Box, r: Box): undefined;    //this相對b超出的部分賦值到r; r的size小于或等于0的話說明完全超出
1432 */
1433 class Box{
1434 
1435     get mx(){
1436         return this.x + this.w;
1437     }
1438 
1439     get my(){
1440         return this.y + this.h;
1441     }
1442 
1443     get cx(){
1444         return this.w / 2 + this.x;
1445     }
1446 
1447     get cy(){
1448         return this.h / 2 + this.y;
1449     }
1450 
1451     constructor(x = 0, y = 0, w = 0, h = 0){
1452         //this.set(x, y, w, h);
1453         this.x = x;
1454         this.y = y;
1455         this.w = w;
1456         this.h = h;
1457     }
1458     
1459     set(x, y, w, h){
1460         this.x = x;
1461         this.y = y;
1462         this.w = w;
1463         this.h = h;
1464         return this;
1465     }
1466 
1467     pos(x, y){
1468         this.x = x;
1469         this.y = y;
1470         return this;
1471     }
1472 
1473     posSub(box){
1474         this.x -= box.x;
1475         this.y -= box.y;
1476         return this;
1477     }
1478 
1479     posSubScalar(v){
1480         this.x -= v;
1481         this.y -= v;
1482         return this;
1483     }
1484     
1485     size(w, h){
1486         this.w = w;
1487         this.h = h;
1488         return this;
1489     }
1490 
1491     sizeMultiply(box){
1492         this.w *= box.w;
1493         this.h *= box.h;
1494         return this;
1495     }
1496 
1497     sizeMultiplyScalar(v){
1498         this.w *= v;
1499         this.h *= v;
1500         
1501         return this;
1502     }
1503 
1504     setFromRotate(rotate){
1505         var minX = this.x, minY = this.y, maxX = 0, maxY = 0;
1506         const point = UTILS.emptyPoint,
1507         run = function (){
1508             if(point.x < minX) minX = point.x;
1509             else if(point.x > maxX) maxX = point.x;
1510             if(point.y < minY) minY = point.y;
1511             else if(point.y > maxY) maxY = point.y;
1512         }
1513 
1514         point.set(this.x, this.y).rotate(rotate.origin, rotate.radian); run();
1515         point.set(this.mx, this.y).rotate(rotate.origin, rotate.radian); run();
1516         point.set(this.mx, this.my).rotate(rotate.origin, rotate.radian); run();
1517         point.set(this.x, this.my).rotate(rotate.origin, rotate.radian); run();
1518 
1519         this.x = minX;
1520         this.y = minY;
1521         this.w = maxX - minX;
1522         this.h = maxY - minY;
1523 
1524         return this;
1525     }
1526 
1527     /* setFromShapeRect(shapeRect){
1528         this.width = shapeRect.width;
1529         this.height = shapeRect.height;
1530         this.x = shapeRect.position.x - this.width / 2;
1531         this.y = shapeRect.position.y - this.height / 2;
1532         return this;
1533     } */
1534 
1535     setFromCircle(circle, inner = true){
1536         if(inner === true){
1537             this.x = Math.sin(-135 / 180 * Math.PI) * circle.r + circle.x;
1538             this.y = Math.cos(-135 / 180 * Math.PI) * circle.r + circle.y;
1539             this.w = this.h = Math.sin(135 / 180 * Math.PI) * circle.r + circle.x - this.x;
1540         }
1541 
1542         else{
1543             this.x = circle.x - circle.r;
1544             this.y = circle.y - circle.r;
1545             this.w = this.h = circle.r * 2;
1546         }
1547         return this;
1548     }
1549 
1550     setFromPolygon(polygon, inner = true){
1551         if(inner === true){
1552             console.warn('Box: 暫不支持第二個引數為true');
1553         }
1554 
1555         else{
1556             const len = polygon.path.length;
1557             let x = Infinity, y = Infinity, mx = -Infinity, my = -Infinity;
1558             for(let k = 0, v; k < len; k+=2){
1559                 v = polygon.path[k];
1560                 if(v < x) x = v;
1561                 else if(v > mx) mx = v;
1562 
1563                 v = polygon.path[k+1];
1564                 if(v < y) y = v;
1565                 else if(v > my) my = v;
1566 
1567             }
1568 
1569             this.set(x, y, mx - x, my - y);
1570 
1571         }
1572         return this;
1573     }
1574 
1575     setFromBoundaryBox(boundaryBox){
1576         this.set(boundaryBox.x, boundaryBox.y, boundaryBox.mx - boundaryBox.x, boundaryBox.my - boundaryBox.y);
1577     }
1578 
1579     toArray(array, index){
1580         array[index] = this.x;
1581         array[index+1] = this.y;
1582         array[index+2] = this.w;
1583         array[index+3] = this.h;
1584 
1585         return this;
1586     }
1587 
1588     copy(box){
1589         this.x = box.x;
1590         this.y = box.y;
1591         this.w = box.w;
1592         this.h = box.h;
1593         return this;
1594         //return Object.assign(this, box); //this.set(box.x, box.y, box.w, box.h);
1595     }
1596     
1597     clone(){
1598         return new this.constructor().copy(this);
1599         //return Object.assign(new this.constructor(), this);
1600     }
1601 
1602     center(box){
1603         this.x = (box.w - this.w) / 2 + box.x;
1604         this.y = (box.h - this.h) / 2 + box.y;
1605         return this;
1606     }
1607 
1608     /* distance(x, y){
1609         return Math.sqrt(Math.pow(this.x - x, 2) + Math.pow(this.y - y, 2));
1610     } */
1611 
1612     distanceFromPoint(x, y, isMax = true){
1613         x -= this.x; y -= this.y;
1614         const cx = this.w / 2 < x ? (isMax === true ? 0 : this.w) : (isMax === true ? this.w : 0), 
1615         cy = this.h / 2 < y ? (isMax === true ? 0 : this.h) : (isMax === true ? this.h : 0);
1616         return Math.sqrt(Math.pow(x - cx, 2) + Math.pow(y - cy, 2));
1617     }
1618 
1619     isEmpty(){
1620         return this.w <= 0 || this.h <= 0;
1621     }
1622 
1623     maxX(){
1624         return this.x + this.w;
1625     }
1626 
1627     maxY(){
1628         return this.y + this.h;
1629     }
1630 
1631     equals(box){
1632         return this.x === box.x && this.w === box.w && this.y === box.y && this.h === box.h;
1633     }
1634 
1635     expand(box){
1636         var v = Math.min(this.x, box.x);
1637         this.w = Math.max(this.x + this.w - v, box.x + box.w - v);
1638         this.x = v;
1639 
1640         v = Math.min(this.y, box.y);
1641         this.h = Math.max(this.y + this.h - v, box.y + box.h - v);
1642         this.y = v;
1643     }
1644 
1645     intersectsBox(box){
1646         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;
1647     }
1648 
1649     containsPoint(x, y){
1650         return x < this.x || x > this.x + this.w || y < this.y || y > this.y + this.h ? false : true;
1651     }
1652 
1653     containsBox(box){
1654         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;
1655     }
1656 
1657     computeOverflow(p, r){
1658         r["copy"](this);
1659         
1660         if(this["x"] < p["x"]){
1661             r["x"] = p["x"];
1662             r["w"] -= p["x"] - this["x"];
1663         }
1664 
1665         if(this["y"] < p["y"]){
1666             r["y"] = p["y"];
1667             r["h"] -= p["y"] - this["y"];
1668         }
1669 
1670         var m = p["x"] + p["w"];
1671         if(r["x"] + r["w"] > m) r["w"] = m - r["x"];
1672 
1673         m = p["y"] + p["h"];
1674         if(r["y"] + r["h"] > m) r["h"] = m - r["y"];
1675     }
1676 
1677 }
1678 
1679 
1680 
1681 
1682 //RoundedRectangle 圓角矩形
1683 class RoundedRectangle extends Box{
1684 
1685     constructor(x, y, w, h, r){
1686         super(x, y, w, h);
1687         this.r = r;
1688     }
1689 
1690     containsPoint(x, y){
1691         if (this.w <= 0 || this.h <= 0) return false;
1692         if (x >= this.x && x <= this.x + this.w) {
1693           if (y >= this.y && y <= this.y + this.h) {
1694             const radius = Math.max(0, Math.min(this.r, Math.min(this.w, this.h) / 2));
1695             if (y >= this.y + radius && y <= this.y + this.h - radius || x >= this.x + radius && x <= this.x + this.w - radius) {
1696               return true;
1697             }
1698             let dx = x - (this.x + radius);
1699             let dy = y - (this.y + radius);
1700             const radius2 = radius * radius;
1701             if (dx * dx + dy * dy <= radius2) {
1702               return true;
1703             }
1704             dx = x - (this.x + this.w - radius);
1705             if (dx * dx + dy * dy <= radius2) {
1706               return true;
1707             }
1708             dy = y - (this.y + this.h - radius);
1709             if (dx * dx + dy * dy <= radius2) {
1710               return true;
1711             }
1712             dx = x - (this.x + radius);
1713             if (dx * dx + dy * dy <= radius2) {
1714               return true;
1715             }
1716           }
1717         }
1718         return false;
1719     }
1720 
1721 }
1722 
1723 
1724 
1725 
1726 /* Circle 圓形
1727 parameter:
1728 attribute:
1729     x,y: Number; 中心點
1730     r: Number; 半徑
1731 
1732     //只讀
1733     r2: Number; //回傳直徑 r*2
1734 
1735 method:
1736     set(x, y, r): this;
1737     pos(x, y): this;
1738     copy(circle: Circle): this;
1739     clone(): Circle;
1740     distance(x, y): Number;
1741     equals(circle: Circle): Bool;
1742     expand(circle: Circle): undefined;                     //擴容; 把circle合并到this
1743     containsPoint(point: Point): Bool; 
1744     intersectsCircle(circle: Circle): Bool;
1745     intersectsBox(box: Box): Bool;
1746     setFromBox(box, inner = true): this;
1747 
1748 */
1749 class Circle{
1750 
1751     get r2(){
1752         return this.r * 2;
1753     }
1754 
1755     constructor(x = 0, y = 0, r = -1){
1756         //this.set(0, 0, -1);
1757         this.x = x;
1758         this.y = y;
1759         this.r = r;
1760     }
1761 
1762     set(x, y, r){
1763         this.x = x;
1764         this.y = y;
1765         this.r = r;
1766 
1767         return this;
1768     }
1769 
1770     setFromPoint(point){
1771         this.x = point.x;
1772         this.y = point.y;
1773         return this;
1774     }
1775 
1776     setFromBox(box, inner = true){
1777         this.x = box.w / 2 + box.x;
1778         this.y = box.h / 2 + box.y;
1779 
1780         if(inner === true) this.r = Math.min(box.w, box.h) / 2;
1781         else this.r = Math.sqrt(box.w + box.h);
1782 
1783         return this;
1784     }
1785 
1786     toArray(array, index){
1787         array[index] = this.x;
1788         array[index+1] = this.y;
1789         array[index+2] = this.r;
1790         
1791         return this;
1792     }
1793 
1794     pos(x, y){
1795         this.x = x;
1796         this.y = y;
1797 
1798         return this;
1799     }
1800 
1801     copy(circle){
1802         this.r = circle.r;
1803         this.x = circle.x;
1804         this.y = circle.y;
1805 
1806         return this;
1807     }
1808 
1809     clone(){
1810 
1811         return new this.constructor().copy(this);
1812 
1813     }
1814 
1815     distance(x, y){
1816         
1817         return Math.sqrt(Math.pow(this.x - x, 2) + Math.pow(this.y - y, 2));
1818 
1819     }
1820 
1821     expand(circle){
1822 
1823     }
1824 
1825     equals(circle){
1826 
1827         return circle.x === this.x && circle.y === this.y && circle.r === this.r;
1828 
1829     }
1830 
1831     containsPoint(point){
1832 
1833         return (Math.pow(point.x - this.x, 2) + Math.pow(point.y - this.y, 2) <= Math.pow(this.r, 2));
1834 
1835     }
1836 
1837     intersectsCircle(circle){
1838 
1839         return (Math.pow(circle.x - this.x, 2) + Math.pow(circle.y - this.y, 2) <= Math.pow(circle.r + this.r, 2));
1840 
1841     }
1842 
1843     intersectsBox(box){
1844         
1845         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));
1846     
1847     }
1848 
1849 }
1850 
1851 
1852 
1853 
1854 //Ellipse 橢圓
1855 class Ellipse {
1856 
1857     constructor(x = 0, y = 0, halfWidth = 0, halfHeight = 0) {
1858         this.x = x;
1859         this.y = y;
1860         this.width = halfWidth;
1861         this.height = halfHeight;
1862     }
1863     
1864     containsPoint(x, y){
1865       if (this.width <= 0 || this.height <= 0) {
1866         return false;
1867       }
1868       let normx = (x - this.x) / this.width;
1869       let normy = (y - this.y) / this.height;
1870       normx *= normx;
1871       normy *= normy;
1872       return normx + normy <= 1;
1873     }
1874 
1875     getBounds() {
1876         return new Box(this.x - this.width, this.y - this.height, this.width, this.height);
1877     }
1878     
1879 }
1880 
1881 
1882 
1883 
1884 /* Polygon 多邊形
1885 parameter: 
1886     points: Array[x, y];
1887 
1888 attribute:
1889 
1890     //只讀屬性
1891     path: Array[x, y]; 
1892 
1893 method:
1894     containsPoint(x, y): Bool;    //x,y是否在多邊形的內部(注意: 在路徑上也回傳 true)
1895     
1896 */
1897 class Polygon{
1898 
1899     get path(){return this.points;}
1900 
1901     constructor(path = []){
1902         this.points = path;
1903     }
1904 
1905     containsPoint(x, y){
1906         const length = this.points.length / 2;
1907         for (let i = 0, j = length - 1; i < length; j = i++) {
1908             const xi = this.points[i * 2];
1909             const yi = this.points[i * 2 + 1];
1910             const xj = this.points[j * 2];
1911             const yj = this.points[j * 2 + 1];
1912             const intersect = yi > y !== yj > y && x < (xj - xi) * ((y - yi) / (yj - yi)) + xi;
1913             if(intersect) return true;
1914         }
1915         return false;
1916     }
1917 
1918     isInPolygon(checkPoint, polygonPoints) {
1919         var counter = 0;
1920         var i;
1921         var xinters;
1922         var p1, p2;
1923         var pointCount = polygonPoints.length;
1924         p1 = polygonPoints[0];
1925         for (i = 1; i <= pointCount; i++) {
1926             p2 = polygonPoints[i % pointCount];
1927             if (
1928                 checkPoint[0] > Math.min(p1[0], p2[0]) &&
1929                 checkPoint[0] <= Math.max(p1[0], p2[0])
1930             ) {
1931                 if (checkPoint[1] <= Math.max(p1[1], p2[1])) {
1932                     if (p1[0] != p2[0]) {
1933                         xinters =
1934                             (checkPoint[0] - p1[0]) *
1935                                 (p2[1] - p1[1]) /
1936                                 (p2[0] - p1[0]) +
1937                             p1[1];
1938                         if (p1[1] == p2[1] || checkPoint[1] <= xinters) {
1939                             counter++;
1940                         }
1941                     }
1942                 }
1943             }
1944             p1 = p2;
1945         }
1946         if (counter % 2 == 0) {
1947             return false;
1948         } else {
1949             return true;
1950         }
1951     }
1952 
1953     containsPolygon(polygon){
1954         const path = polygon.path, len = path.length;
1955         for(let k = 0; k < len; k += 2){
1956             if(this.containsPoint(path[k], path[k+1]) === false) return false;
1957         }
1958 
1959         return true;
1960     }
1961 
1962     toPoints(){
1963         const path = this.path, len = path.length, result = [];
1964         
1965         for(let k = 0; k < len; k += 2) result.push(new Point(path[k], path[k+1]));
1966 
1967         return result;
1968     }
1969 
1970     toLines(){
1971         const path = this.path, len = path.length, result = [];
1972         
1973         for(let k = 0, x = NaN, y; k < len; k += 2){
1974 
1975             if(isNaN(x)){
1976                 x = path[k];
1977                 y = path[k+1];
1978                 continue;
1979             }
1980 
1981             const line = new Line(x, y, path[k], path[k+1]);
1982             
1983             x = line.x1;
1984             y = line.y1;
1985 
1986             result.push(line);
1987 
1988         }
1989 
1990         return result;
1991     }
1992 
1993     merge(polygon){
1994 
1995         const linesA = this.toLines(), linesB = polygon.toLines(), nodes = [], newLines = [],
1996         
1997         pointA = new Point(), pointB = new Point(),
1998 
1999         forEachNodes = (pathA, pathB, funcA = null, funcB = null) => {
2000             for(let k = 0, lenA = pathA.length, lenB = pathB.length; k < lenA; k++){
2001                 if(funcA !== null) funcA(pathA[k]);
2002 
2003                 for(let i = 0; i < lenB; i++){
2004                     if(funcB !== null) funcB(pathB[i], pathA[k]);
2005                 }
2006     
2007             }
2008         }
2009 
2010         if(this.containsPolygon(polygon)){console.log('this -> polygon');
2011             forEachNodes(linesA, linesB, lineA => newLines.push(lineA), (lineB, lineA) => {
2012                 if(lineA.intersectPoint(lineB, pointA) === pointA) newLines.push(pointA.clone());
2013             });
2014 
2015             return newLines;
2016         }
2017 
2018         //收集所有的交點 (保存至 line)
2019         forEachNodes(linesA, linesB, lineA => lineA.nodes = [], (lineB, lineA) => {
2020             if(lineB.nodes === undefined) lineB.nodes = [];
2021             if(lineA.intersectPoint(lineB, pointA) === pointA){
2022                 const node = {
2023                     lineA: lineA, 
2024                     lineB: lineB, 
2025                     point: pointA.clone(),
2026                     disA: pointA.distanceCompare(pointB.set(lineA.x, lineA.y)),
2027                     disB: pointA.distanceCompare(pointB.set(lineB.x, lineB.y)),
2028                 }
2029                 lineA.nodes.push(node);
2030                 lineB.nodes.push(node);
2031                 nodes.push(node);
2032             }
2033         });
2034 
2035         //交點以原點為目標排序
2036         for(let k = 0, sotr = function (a,b){return a.disA - b.disA;}, countA = linesA.length; k < countA; k++) linesA[k].nodes.sort(sotr);
2037         for(let k = 0, sotr = function (a,b){return a.disB - b.disB;}, countB = linesB.length; k < countB; k++) linesB[k].nodes.sort(sotr);
2038 
2039         var _loopTypeA, _loopTypeB;
2040         const result_loop = {
2041             lines: null,
2042             loopType: '',
2043             line: null,
2044             count: 0,
2045             indexed: 0,
2046         },
2047         
2048         //遍歷某條線
2049         loop = (lines, index, loopType) => {
2050             const length = lines.length, indexed = lines.length, model = lines === linesA ? polygon : this;
2051         
2052             var line, i = 1;
2053             while(true){
2054                 if(loopType === 'next') index = index === length - 1 ? 0 : index + 1;
2055                 else if(loopType === 'back') index = index === 0 ? length - 1 : index - 1;
2056                 line = lines[index];
2057 
2058                 result_loop.count = line.nodes.length;
2059                 if(result_loop.count !== 0){
2060                     result_loop.lines = lines;
2061                     result_loop.loopType = loopType;
2062                     result_loop.line = line;
2063                     result_loop.indexed = index;
2064                     if(loopType === 'next') addLine(line, model);
2065 
2066                     return result_loop;
2067                 }
2068                 
2069                 addLine(line, model);
2070                 if(indexed === i++) break;
2071 
2072             }
2073             
2074         },
2075 
2076         //更新或創建交點的索引
2077         setNodeIndex = (lines, index, loopType) => {
2078             const line = lines[index], count = line.nodes.length;
2079             if(loopType === undefined) loopType = lines === linesA ? _loopTypeA : _loopTypeB;
2080 
2081             if(loopType === undefined) return;
2082             
2083             if(line.nodeIndex === undefined){
2084                 line.nodeIndex = loopType === 'next' ? 0 : count - 1;
2085                 line.nodeState = count === 1 ? 'end' : 'start';
2086             
2087             }
2088 
2089             else{
2090                 if(line.nodeState === 'end' || line.nodeState === ''){
2091                     line.nodeState = '';
2092                     return;
2093                 }
2094 
2095                 if(loopType === 'next'){
2096                     line.nodeIndex += 1;
2097 
2098                     if(line.nodeIndex === count - 1) line.nodeState = 'end';
2099                     else line.nodeState = 'run';
2100                 }
2101 
2102                 else if(loopType === 'back'){
2103                     line.nodeIndex -= 1;
2104 
2105                     if(line.nodeIndex === 0) line.nodeState = 'end';
2106                     else line.nodeState = 'run';
2107                 }
2108 
2109             }
2110 
2111         },
2112 
2113         //只有在跳線的時候才執行此方法, 如果跳線的話: 某條線上的交點必然在兩端;
2114         getLoopType = (lines, index, nodePoint) => {
2115             const line = lines[index], lineNext = lines[index === lines.length - 1 ? 0 : index + 1],
2116 
2117             model = lines === linesA ? polygon : this,
2118             isLineBack = newLines.includes(line) === false && model.containsPoint(line.x, line.y) === false,
2119             isLineNext = newLines.includes(lineNext) === false && model.containsPoint(lineNext.x, lineNext.y) === false;
2120             
2121             if(isLineBack && isLineNext){
2122                 const len = line.nodes.length;
2123                 if(len >= 2){
2124                     if(line.nodes[len - 1].point.equals(nodePoint)) return 'next';
2125                     else if(line.nodes[0].point.equals(nodePoint)) return 'back';
2126                 }
2127                 
2128                 else console.warn('路徑復雜', line);
2129                 
2130             }
2131 
2132             else if(isLineNext){
2133                 return 'next';
2134             }
2135 
2136             else if(isLineBack){
2137                 return 'back';
2138             }
2139 
2140             return '';
2141         },
2142 
2143         //添加線至新的形狀陣列
2144         addLine = (line, model) => {
2145             //if(newLines.includes(line) === false && model.containsPoint(line.x, line.y) === false) newLines.push(line);
2146             if(line.nodes.length === 0) newLines.push(line);
2147             else if(model.containsPoint(line.x, line.y) === false) newLines.push(line);
2148             
2149         },
2150 
2151         //處理擁有交點的線
2152         computeNodes = v => {
2153             if(v === undefined || v.count === 0) return;
2154             
2155             setNodeIndex(v.lines, v.indexed, v.loopType);
2156         
2157             //添加交點
2158             const node = v.line.nodes[v.line.nodeIndex];
2159             if(newLines.includes(node.point) === false) newLines.push(node.point);
2160             else return;
2161 
2162             var lines = v.lines === linesA ? linesB : linesA, 
2163             line = lines === linesA ? node.lineA : node.lineB, 
2164             index = lines.indexOf(line);
2165 
2166             setNodeIndex(lines, index);
2167         
2168             //選擇交點狀態
2169             var nodeState = line.nodeState !== undefined ? line.nodeState : v.line.nodeState;
2170             if((v.count <= 2 && line.nodes.length > 2) || (v.count > 2 && line.nodes.length <= 2)){
2171                 if(line.nodeState === 'start'){
2172                     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];
2173                     
2174                     if(newLines.includes(backLine) && backLine.nodes.length === 0){
2175                         nodeState = 'run';
2176                     }
2177 
2178                 }
2179                 else if(line.nodeState === 'end'){
2180                     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];
2181                     const model = v.lines === linesA ? polygon : this;
2182                     if(model.containsPoint(nextLine.x, nextLine.y) === false && nextLine.nodes.length === 0){
2183                         nodeState = 'run';
2184                     }
2185                     
2186                 }
2187             }
2188 
2189             switch(nodeState){
2190 
2191                 //不跳線
2192                 case 'run': 
2193                     if(v.loopType === 'back') addLine(v.line, v.lines === linesA ? polygon : this);
2194                     return computeNodes(loop(v.lines, v.indexed, v.loopType));
2195 
2196                 //跳線
2197                 case 'start': 
2198                 case 'end': 
2199                     const loopType = getLoopType(lines, index, node.point);
2200                     if(loopType !== ''){
2201                         if(lines === linesA) _loopTypeA = loopType;
2202                         else _loopTypeB = loopType;
2203                         if(loopType === 'back') addLine(line, v.lines === linesA ? this : polygon);
2204                         return computeNodes(loop(lines, index, loopType));
2205                     }
2206                     break;
2207 
2208             }
2209 
2210         }
2211         
2212         //獲取介入點
2213         var startLine = null;
2214         for(let k = 0, len = nodes.length, node; k < len; k++){
2215             node = nodes[k];
2216             if(node.lineA.nodes.length !== 0){
2217                 startLine = node.lineA;
2218                 if(node.lineA.nodes.length === 1 && node.lineB.nodes.length > 1){
2219                     startLine = node.lineB.nodes[0].lineB;
2220                     result_loop.lines = linesB;
2221                     result_loop.loopType = _loopTypeB = 'next';
2222                 }
2223                 else{
2224                     result_loop.lines = linesA;
2225                     result_loop.loopType = _loopTypeA = 'next';
2226                 }
2227                 result_loop.line = startLine;
2228                 result_loop.count = startLine.nodes.length;
2229                 result_loop.indexed = result_loop.lines.indexOf(startLine);
2230                 break;
2231             }
2232         }
2233 
2234         if(startLine === null){
2235             console.warn('Polygon: 找不到介入點, 終止了合并');
2236             return newLines;
2237         }
2238 
2239         computeNodes(result_loop);
2240     
2241         return newLines;
2242     }
2243 
2244 }
2245 
2246 
2247 
2248 
2249 /* Meter 計量器
2250 parameter: 
2251     min = -100, 
2252     max = 100, 
2253     value = https://www.cnblogs.com/weihexinCode/p/0
2254 
2255 attribute: 
2256     min, max, value: number;
2257     ratio: number; //只讀; 回傳value與min至max之間的比率;
2258 
2259 method: 
2260     setFromRatio(r: number): undefined;    //通過比率設定value
2261     normalize(): undefined;
2262 */
2263 class Meter{
2264     
2265     #v = 0;
2266     get value(){return this.#v;}
2267     set value(v){
2268         if(v < this.min) v = this.min;
2269         else if(v > this.max) v = this.max;
2270         if(v !== this.#v) this.#v = v;
2271     }
2272 
2273     get ratio(){
2274         return (this.#v - this.min) / (this.max - this.min);
2275     }
2276 
2277     constructor(min = -100, max = 100, value){
2278         this.min = min;
2279         this.max = max;
2280         if(value !== undefined) this.value =https://www.cnblogs.com/weihexinCode/p/ value;
2281     }
2282 
2283     setFromRatio(r){
2284         this.value = https://www.cnblogs.com/weihexinCode/p/r * (this.max - this.min) + this.min;
2285     }
2286 
2287     normalize(){
2288         const ratio = this.ratio;
2289         this.min = 0;
2290         this.max = 1;
2291         this.#v = ratio * 1;
2292     }
2293 
2294 }
2295 
2296 
2297 
2298 
2299 /* RGBColor
2300 parameter: 
2301     r, g, b
2302 
2303 method:
2304     set(r, g, b: Number): this;            //rgb: 0 - 255; 第一個引數可以為 css color
2305     setFormHex(hex: Number): this;         //
2306     setFormHSV(h, s, v: Number): this;    //h:0-360; s,v:0-100; 顏色, 明度, 暗度
2307     setFormString(str: String): Number;    //str: 英文|css color; 回傳的是透明度 (如果為 rgba 則回傳a; 否則總是回傳1)
2308 
2309     copy(v: RGBColor): this;
2310     clone(): RGBColor;
2311 
2312     getHex(): Number;
2313     getHexString(): String;
2314     getHSV(result: Object{h, s, v}): result;    //result: 默認是一個新的Object
2315     getRGBA(alpha: Number): String;             //alpha: 0 - 1; 默認 1
2316     getStyle()                                     //.getRGBA()別名
2317 
2318     stringToColor(str: String): String; //str 轉為 css color; 如果str不是color格式則回傳 ""
2319 
2320 */
2321 class RGBColor{
2322 
2323     get isRGBColor(){return true;}
2324 
2325     constructor(r = 255, g = 255, b = 255){
2326         this.r = r;
2327         this.g = g;
2328         this.b = b;
2329     }
2330 
2331     copy(v){
2332         this.r = v.r;
2333         this.g = v.g;
2334         this.b = v.b;
2335         return this;
2336     }
2337 
2338     clone(){
2339         return new this.constructor().copy(this);
2340     }
2341 
2342     set(r, g, b){
2343         if(typeof r !== "string"){
2344             this.r = UTILS.isNumber(r) ? r : 255;
2345             this.g = UTILS.isNumber(g) ? g : 255;
2346             this.b = UTILS.isNumber(b) ? b : 255;
2347         }
2348 
2349         else this.setFormString(r);
2350         
2351         return this;
2352     }
2353 
2354     setFormHex(hex){
2355         hex = Math.floor( hex );
2356 
2357         this.r = hex >> 16 & 255;
2358         this.g = hex >> 8 & 255;
2359         this.b = hex & 255;
2360         return this;
2361     }
2362 
2363     setFormHSV(h, s, v){
2364         h = h >= 360 ? 0 : h;
2365         var s=s/100;
2366         var v=v/100;
2367         var h1=Math.floor(h/60) % 6;
2368         var f=h/60-h1;
2369         var p=v*(1-s);
2370         var q=v*(1-f*s);
2371         var t=v*(1-(1-f)*s);
2372         var r,g,b;
2373         switch(h1){
2374             case 0:
2375                 r=v;
2376                 g=t;
2377                 b=p;
2378                 break;
2379             case 1:
2380                 r=q;
2381                 g=v;
2382                 b=p;
2383                 break;
2384             case 2:
2385                 r=p;
2386                 g=v;
2387                 b=t;
2388                 break;
2389             case 3:
2390                 r=p;
2391                 g=q;
2392                 b=v;
2393                 break;
2394             case 4:
2395                 r=t;
2396                 g=p;
2397                 b=v;
2398                 break;
2399             case 5:
2400                 r=v;
2401                 g=p;
2402                 b=q;
2403                 break;
2404         }
2405 
2406         this.r = Math.round(r*255);
2407         this.g = Math.round(g*255);
2408         this.b = Math.round(b*255);
2409         return this;
2410     }
2411 
2412     setFormString(color){
2413         if(typeof color !== "string") return 1;
2414         var _color = this.stringToColor(color);
2415         
2416         if(_color[0] === "#"){
2417             const len = _color.length;
2418             if(len === 4){
2419                 _color = _color.slice(1);
2420                 this.setFormHex(parseInt("0x"+_color + "" + _color));
2421             }
2422             else if(len === 7) this.setFormHex(parseInt("0x"+_color.slice(1)));
2423         }
2424 
2425         else if(_color[0] === "r" && _color[1] === "g" && _color[2] === "b"){
2426             const arr = [];
2427             for(let k = 0, len = _color.length, v = "", is = false; k < len; k++){
2428                 
2429                 if(is === true){
2430                     if(_color[k] === "," || _color[k] === ")"){
2431                         arr.push(parseFloat(v));
2432                         v = "";
2433                     }
2434                     else v += _color[k];
2435                     
2436                 }
2437 
2438                 else if(_color[k] === "(") is = true;
2439                 
2440             }
2441             
2442             this.set(arr[0], arr[1], arr[2]);
2443             return arr[3] === undefined ? 1 : arr[3];
2444         }
2445         
2446         return 1;
2447     }
2448 
2449     getHex(){
2450 
2451         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;
2452 
2453     }
2454 
2455     getHexString(){
2456 
2457         return '#' + ( '000000' + this.getHex().toString( 16 ) ).slice( - 6 );
2458 
2459     }
2460 
2461     getHSV(result){
2462         result = result || {}
2463         var r=this.r/255;
2464         var g=this.g/255;
2465         var b=this.b/255;
2466         var h,s,v;
2467         var min=Math.min(r,g,b);
2468         var max=v=Math.max(r,g,b);
2469         var l=(min+max)/2;
2470         var difference = max-min;
2471         
2472         if(max==min){
2473             h=0;
2474         }else{
2475             switch(max){
2476                 case r: h=(g-b)/difference+(g < b ? 6 : 0);break;
2477                 case g: h=2.0+(b-r)/difference;break;
2478                 case b: h=4.0+(r-g)/difference;break;
2479             }
2480             h=Math.round(h*60);
2481         }
2482         if(max==0){
2483             s=0;
2484         }else{
2485             s=1-min/max;
2486         }
2487         s=Math.round(s*100);
2488         v=Math.round(v*100);
2489         result.h = h;
2490         result.s = s;
2491         result.v = v;
2492         return result;
2493     }
2494 
2495     getStyle(){
2496         return this.getRGBA(1);
2497     }
2498 
2499     getRGBA(alpha = 1){
2500         return `rgba(${this.r},${this.g},${this.b},${alpha})`;
2501     }
2502 
2503     stringToColor(str){
2504         var _color = "";
2505         for(let k = 0, len = str.length; k < len; k++){
2506             if(str[k] === " ") continue;
2507             _color += str[k];
2508         }
2509         
2510         if(_color[0] === "#" || (_color[0] === "r" && _color[1] === "g" && _color[2] === "b")) return _color;
2511 
2512         return UTILS.colorTable[_color] || "";
2513     }
2514 
2515 }
2516 
2517 
2518 
2519 
2520 /* Timer 定時器
2521 
2522 parameter:
2523     func: Function; //定時器運行時的回呼; 默認 null, 如果定義構造器將自動呼叫一次.restart()方法
2524     speed: Number; //延遲多少毫秒執行一次 func; 默認 3000;
2525     step: Integer; //執行多少次: 延遲speed毫秒執行一次 func; 默認 Infinity;
2526     
2527 attribute:
2528     func, speed, step;    //這些屬性可以隨時更改;
2529 
2530 method:
2531     start(func, speed): this;    //啟動定時器 (如果定時器正在運行則什么都不會做)
2532     stop(): undefined;            //停止定時器
2533 
2534 demo:
2535     //每 3000 毫秒 列印一次 timer.number
2536     new Timer(timer => {
2537         console.log(timer.number);
2538     }, 3000);
2539 
2540 */
2541 class Timer{
2542 
2543     #i = 0;
2544     #id = -1;
2545 
2546     get running(){
2547         return this.#id !== -1;
2548     }
2549 
2550     constructor(func = null, speed = 3000, step = Infinity, isStart = true){
2551         this.func = func;
2552         this.speed = speed;
2553         this.step = step;
2554         this.onend = null;
2555         
2556         const animate = () => {
2557             this.#i++;
2558             this.func(this);
2559             if(this.#id !== -1){
2560                 if(this.#i < this.step) this.#id = setTimeout(animate, this.speed); 
2561                 else{
2562                     this.#id = -1;
2563                     if(this.onend !== null) this.onend(this);
2564                 }
2565             }
2566         }
2567 
2568         this._animate = animate;
2569         if(isStart === true && typeof this.func === "function"){
2570             this.#id = setTimeout(this._animate, this.speed);
2571             this.#i = 0;
2572         }
2573     }
2574 
2575     restart(){
2576         if(this.#id !== -1) clearTimeout(this.#id);
2577         this.#id = setTimeout(this._animate, this.speed);
2578         this.#i = 0;
2579     }
2580 
2581     start(func, speed){
2582         if(this.#id === -1){
2583             if(typeof func === 'function') this.func = func;
2584             if(UTILS.isNumber(speed) === true) this.speed = speed;
2585             this.#id = setTimeout(this._animate, this.speed);
2586             this.#i = 0;
2587         }
2588     }
2589 
2590     stop(){
2591         if(this.#id !== -1){
2592             clearTimeout(this.#id);
2593             this.#id = -1;
2594         }
2595     }
2596 
2597 }
2598 
2599 
2600 
2601 
2602 /* SeekPath A*尋路
2603 parameter: 
2604     option: Object{
2605         angle: Number,         //8 || 16
2606         timeout: Number,     //單位為毫秒
2607         size: Number,         //每格的寬高
2608         lenX, lenY: Number,    //長度
2609         disables: Array[0||1],
2610         heights: Array[Number],
2611         path: Array[], //存放尋路結果 默認創建一個空陣列
2612     }
2613 
2614 attribute:
2615     size: Number;     //每個索引的大小
2616     lenX: Number;     //最大長度x (設定此屬性時, 你需要重新.initMap(heights);)
2617     lenY: Number;     //最大長度y (設定此屬性時, 你需要重新.initMap(heights);)
2618 
2619     //此屬性已廢棄 range: Box;            //本次的搜索范圍, 默認: 0,0,lenX,lenY
2620     angle: Number;         //8四方向 或 16八方向 默認 16
2621     timeout: Number;     //超時毫秒 默認 500
2622     mapRange: Box;        //地圖box
2623     //此屬性已廢棄(run方法不在檢測相鄰的高) maxHeight: Number;     //相鄰可走的最大高 默認 6
2624 
2625     //只讀屬性
2626     success: Bool;            //只讀; 本次搜索是否成功找到終點; (如果為false說明.run()回傳的是 距終點最近的路徑; 超時也會被判定為false)
2627     path: Array[node];    //存放.run()回傳的路徑
2628     map: Map;                 //地圖的快取資料
2629 
2630 method:
2631     initMap(heights: Array[Number]): undefiend;     //初始化類時自動呼叫一次; heights:如果你的場景存在高請定義此引數
2632     run(x, y, x1, y1: Number): Array[x, y, z];         //引數索引坐標
2633     getDots(x, y, a, r): Array[ix, iy];             //獲取周圍的點 x,y, a:8|16, r:存放結果陣列
2634     getLegalPoints(ix, iy, count, result = []): Array[node];    //獲取 ix, iy 周圍 合法的, 相鄰的 count 個點
2635 
2636 demo:
2637     const sp = new SeekPath({
2638         angle: 16,
2639         timeout: 500,
2640         size: 10,
2641         lenX: 1000,
2642         lenY: 1000,
2643     }),
2644 
2645     path = sp.run(0, 0, 1000, 1000);
2646 
2647     console.log(sp);
2648 */
2649 class SeekPath{
2650 
2651     static _open = []
2652     static _dots = [] //.run() .getLegalPoints()
2653     static dots4 = []; //._check()
2654     static _sort = function (a, b){return a["f"] - b["f"];}
2655 
2656     #map = null;
2657     #path = null;
2658     #success = true;
2659     #halfX = 50;
2660     #halfY = 50;
2661 
2662     #size = 10;
2663     #lenX = 10;
2664     #lenY = 10;
2665 
2666     constructor(option = {}){
2667         this.angle = (option.angle === 8 || option.angle === 16) ? option.angle : 16; //8四方向 或 16八方向
2668         this.timeout = option.timeout || 500; //超時毫秒
2669         //this.maxHeight = option.maxHeight || 6;
2670         this.mapRange = new Box();
2671         this.size = option.size || 10;
2672         this.lenX = option.lenX || 10;
2673         this.lenY = option.lenY || 10;
2674         this.#path = Array.isArray(option.path) ? option.path : [];
2675         this.initMap(option.disable, option.height);
2676         option = undefined;
2677     }
2678 
2679     //this.#map[x][y] = Object{height: 此點的高,默認0, is: 此點是否可走,默認1(disable)};
2680     get map(){
2681         return this.#map;
2682     }
2683 
2684     //this.#path = Array[x,y,z]
2685     get path(){
2686         return this.#path;
2687     }
2688 
2689     get success(){
2690         return this.#success;
2691     }
2692 
2693     get size(){
2694         return this.#size;
2695     }
2696 
2697     set size(v){
2698         this.#size = v;
2699         v = v / 2;
2700         this.#halfX = v * this.#lenX;
2701         this.#halfY = v * this.#lenY;
2702     }
2703 
2704     get lenX(){
2705         return this.#lenX;
2706     }
2707 
2708     set lenX(v){
2709         this.#lenX = v;
2710         v = this.#size / 2;
2711         this.#halfX = v * this.#lenX;
2712         this.#halfY = v * this.#lenY;
2713     }
2714 
2715     get lenY(){
2716         return this.#lenY;
2717     }
2718 
2719     set lenY(v){
2720         this.#lenY = v;
2721         v = this.#size / 2;
2722         this.#halfX = v * this.#lenX;
2723         this.#halfY = v * this.#lenY;
2724     }
2725 
2726     getNode(sx, sy){
2727         sx = this.#map[Math.floor((this.#halfX + sx) / this.#size)];
2728         if(sx !== undefined) return sx[Math.floor((this.#halfY + sy) / this.#size)];
2729     }
2730 
2731     node(ix, iy){
2732         ix = this.#map[ix];
2733         if(ix !== undefined) return ix[iy];
2734     }
2735     
2736     toScene(n, v){ //n = "x|y"
2737         //n = n === "y" ? "lenY" : "lenX";
2738         if(n === "x") return v * this.#size - this.#halfX;
2739         return v * this.#size - this.#halfY;
2740     }
2741     
2742     toIndex(n, v){
2743         //n = n === "y" ? "lenY" : "lenX";
2744         if(n === "x") return Math.floor((this.#halfX + v) / this.#size);
2745         return Math.floor((this.#halfY + v) / this.#size);
2746     }
2747 
2748     initMap(disable, height){
2749         
2750         disable = Array.isArray(disable) === true ? disable : null;
2751         height = Array.isArray(height) === true ? height : null;
2752         
2753         const lenX = this.lenX, lenY = this.lenY;
2754         var getHeight = (ix, iy) => {
2755             if(height === null) return 0;
2756             ix = height[ix * lenY + iy];
2757             if(ix === undefined) return 0;
2758             return ix;
2759         },
2760         getDisable = (ix, iy) => {
2761             if(disable === null) return 1;
2762             ix = disable[ix * lenY + iy];
2763             if(ix === undefined) return 0;
2764             return ix;
2765         },
2766 
2767         map = []//new Map();
2768 
2769         for(let x = 0, y, m; x < lenX; x++){
2770             m = []//new Map();
2771             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:""});
2772             map[x] = m;//map.set(x, m);
2773         }
2774         
2775         this.#map = map;
2776         this._id = -1;
2777         this._updateID();
2778         this.mapRange.set(0, 0, this.#lenX-1, this.#lenY-1);
2779 
2780         map = disable = height = getHeight = undefined;
2781 
2782     }
2783 
2784     getLegalPoints(ix, iy, count, result = []){  //不包括 ix, iy
2785         const sTime = UTILS.time, _dots = SeekPath._dots;
2786         result.length = 0;
2787         result[0] = this.#map[ix][iy];
2788         count += 1;
2789         
2790         while(result.length < count){
2791             for(let k = 0, i, n, d, len = result.length; k < len; k++){
2792                 n = result[k];
2793                 this.getDots(n.x, n.y, this.angle, _dots);
2794                 for(i = 0; i < this.angle; i += 2){
2795                     d = this.#map[_dots[i]][_dots[i+1]];
2796                     if(d.is === 1 && this.mapRange.containsPoint(d.x, d.y) && !result.includes(d)){
2797                         if(Math.abs(n.x - d.x) + Math.abs(n.y - d.y) === 2 && this._check(n, d)){
2798                             result.push(d);
2799                         }
2800                     }
2801                 }
2802             }
2803 
2804             if(UTILS.time - sTime >= this.timeout) break;
2805         }
2806     
2807         result.splice(0, 1);
2808         return result;
2809     }
2810 
2811     getLinePoints(now, next, count, result = []){ //不包括 now 
2812         if(count % 2 !== 0) count += 1;
2813 
2814         const len = count / 2, angle90 = 90/180*Math.PI;
2815 
2816         var i, ix, iy, n, nn = next, is = false;
2817 
2818         UTILS.emptyPoint.set(now.x, now.y).rotate(next, angle90);
2819         var disX = UTILS.emptyPoint.x - next.x, 
2820         disY = UTILS.emptyPoint.y - next.y;
2821         
2822         for(i = 0; i < len; i++){
2823             if(is){
2824                 result[len-1-i] = nn;
2825                 continue;
2826             }
2827             ix = disX + disX * i + next.x;
2828             iy = disY + disY * i + next.y;
2829 
2830             n = this.#map[ix][iy];
2831             if(n.is === 1) nn = n;
2832             else is = true;
2833             result[len-1-i] = nn;
2834         }
2835 
2836         //result[len] = next;
2837         is = false;
2838         nn = next;
2839 
2840         UTILS.emptyPoint.set(now.x, now.y).rotate(next, -angle90);
2841         disX = UTILS.emptyPoint.x - next.x, 
2842         disY = UTILS.emptyPoint.y - next.y;
2843 
2844         for(i = 0; i < len; i++){
2845             if(is){
2846                 result[len+i] = nn;
2847                 continue;
2848             }
2849             ix = disX + disX * i + next.x;
2850             iy = disY + disY * i + next.y;
2851 
2852             n = this.#map[ix][iy];
2853             if(n.is === 1) nn = n;
2854             else is = true;
2855             result[len+i] = nn;
2856         }
2857 
2858         return result;
2859     }
2860 
2861     getDots(x, y, a, r = []){ //獲取周圍的點 x,y, a:8|16, r:存放結果陣列
2862         r.length = 0;
2863         const x_1 = x-1, x1 = x+1, y_1 = y-1, y1 = y+1;
2864         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);
2865         else r.push(x, y_1, x, y1, x_1, y, x1, y);
2866     }
2867 
2868     getDisMHD(nodeA, nodeB){
2869         return Math.abs(nodeA.x - nodeB.x) + Math.abs(nodeA.y - nodeB.y);
2870     }
2871 
2872     _updateID(){ //更新標記
2873         this._id++;
2874         this._openID = "o_"+this._id;
2875         this._closeID = "c_"+this._id;
2876     }
2877 
2878     _check(dotA, dotB){ //檢測 a 是否能到 b
2879         //獲取 dotB 周圍的4個點 并 遍歷這4個點
2880         this.getDots(dotB.x, dotB.y, 8, SeekPath.dots4);
2881         for(let k = 0, x, y; k < 8; k += 2){
2882             x = SeekPath.dots4[k]; 
2883             y = SeekPath.dots4[k+1];
2884             if(this.mapRange.containsPoint(x, y) === false) continue;
2885 
2886             //找出 dotA 與 dotB 相交的兩個點:
2887             if(Math.abs(dotA.x - x) + Math.abs(dotA.y - y) === 1){
2888                 //如果其中一個交點是不可走的則 dotA 到 dotB 不可走, 既回傳 false
2889                 if(this.#map[x][y].is === 0) return false;
2890             }
2891 
2892         }
2893 
2894         return true;
2895     }
2896 
2897     run(x, y, x1, y1, path = this.#path){
2898         path.length = 0;
2899         if(this.#map === null || this.mapRange.containsPoint(x, y) === false) return path;
2900         
2901         var _n = this.#map[x][y];
2902         if(_n.is === 0) return path;
2903 
2904         const _sort = SeekPath._sort,
2905         _open = SeekPath._open,
2906         _dots = SeekPath._dots, 
2907         time = Date.now();
2908 
2909         //var isDot = true, 
2910         var suc = _n, k, mhd, g, h, f, _d;
2911 
2912         _n.g = 0;
2913         _n.h = _n.h = Math.abs(x1 - x) * 10 + Math.abs(y1 - y) * 10; 
2914         _n.f = _n.h;
2915         _n.p = null;
2916         this._updateID();
2917         _n.id = this._openID;
2918         _open.push(_n);
2919         
2920         while(_open.length !== 0){
2921             if(Date.now() - time > this.timeout) break;
2922 
2923             _open.sort(_sort);
2924             _n = _open.shift();
2925             if(_n.x === x1 && _n.y === y1){
2926                 suc = _n;
2927                 break;
2928             }
2929             
2930             if(suc.h > _n.h) suc = _n;
2931             _n.id = this._closeID;
2932             this.getDots(_n.x, _n.y, this.angle, _dots);
2933             
2934             for(k = 0; k < this.angle; k += 2){
2935                 
2936                 _d = this.#map[_dots[k]][_dots[k+1]];
2937                 if(_d.id === this._closeID || _d.is === 0 || this.mapRange.containsPoint(_d.x, _d.y) === false) continue;
2938 
2939                 mhd = Math["abs"](_n["x"] - _d.x) + Math["abs"](_n["y"] - _d.y);
2940                 g = _n["g"] + (mhd === 1 ? 10 : 14);
2941                 h = Math["abs"](x1 - _d.x) * 10 + Math["abs"](y1 - _d.y) * 10;
2942                 f = g + h;
2943             
2944                 if(_d.id !== this._openID){
2945                     //如果是斜角和8方向:
2946                     if(mhd !== 1 && this.angle === 16){
2947                         if(this._check(_n, _d)){
2948                             _d.g = g;
2949                             _d.h = h;
2950                             _d.f = f;
2951                             _d.p = _n;
2952                             _d.id = this._openID;
2953                             _open.push(_d);
2954                         }
2955                     }else{
2956                         _d.g = g;
2957                         _d.h = h;
2958                         _d.f = f;
2959                         _d.p = _n;
2960                         _d.id = this._openID;
2961                         _open.push(_d);
2962                     }
2963                 }
2964 
2965                 else if(g < _d.g){
2966                     _d.g = g;
2967                     _d.f = g + _d.h;
2968                     _d.p = _n;
2969                 }
2970     
2971             }
2972         }
2973 
2974         this.#success = suc === _n;
2975 
2976         while(suc !== null){
2977             path.unshift(suc); //0為起始點,length-1為結束點
2978             //path.unshift(this.toScene("x", suc["x"]), suc["height"], this.toScene("y", suc["y"]));
2979             suc = suc["p"];
2980         }
2981         
2982         _open.length = _dots.length = 0;
2983         
2984         return path;
2985     }
2986 
2987 }
2988 
2989 
2990 
2991 
2992 /* TweenValue (從 原點 以規定的時間到達  終點)
2993 
2994 parameter: origin, end, time, onUpdate, onEnd;
2995 
2996 attribute:
2997     origin: Object; //原點(起點)
2998     end: Object; //終點
2999     time: Number; //origin 到 end 花費的時間
3000     onUpdate: Function; //更新回呼; 一個回呼引數 origin; 默認null;
3001     onEnd: Function; //結束回呼; 沒有回呼引數; 默認null; (如果回傳的是"restart"將不從佇列洗掉, 你可以在onEnd中更新.end不間斷的繼續補間)
3002 
3003 method:
3004     reset(origin, end: Object): undefined; //更換 .origin, .end; 它會清除其它物件的快取屬性
3005     reverse(): undefined; //this.end 復制 this.origin 的原始值
3006     update(): undefined; //Tween 通過此方法統一更新 TweenValue
3007 
3008 demo: 
3009     //init Tween:
3010     const tween = new Tween(),
3011     animate = function (){
3012         requestAnimationFrame(animate);
3013         tween.update();
3014     }
3015 
3016     //init TweenValue:
3017     const v1 = new Tween.Value({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v));
3018     
3019     animate();
3020     tween.start(v1);
3021 
3022     //緩動
3023     const end = 100;
3024     var step, left = 0;
3025     new Timer(timer => {
3026         step = (end - left) / 10;
3027         left += step;
3028         if(Math.ceil(left) === end) timer.stop();
3029     }, 1000);
3030 */
3031 class TweenValue{
3032 
3033     constructor(origin = {}, end = {}, time = 500, onEnd = null, onUpdate = null, onStart = null){
3034         this.origin = origin;
3035         this.end = end;
3036         this.time = time;
3037 
3038         this.onUpdate = onUpdate;
3039         this.onEnd = onEnd;
3040         this.onStart = onStart;
3041         
3042         //以下屬性不能直接設定
3043         this._r = null;
3044         this._t = 0;
3045         this._v = Object.create(null);
3046     }
3047 
3048     _start(){
3049         var v = "";
3050         for(v in this.origin) this._v[v] = this.origin[v];
3051         if(this.onStart !== null) this.onStart(this);
3052         this._t = Date.now();
3053         //this.update();
3054     }
3055 
3056     reset(origin, end){
3057         this.origin = origin;
3058         this.end = end;
3059         this._v = Object.create(null);
3060     }
3061 
3062     reverse(){
3063         var n = "";
3064         for(n in this.origin) this.end[n] = this._v[n];
3065     }
3066 
3067     update(){
3068 
3069         if(this["_r"] !== null){
3070 
3071             var ted = Date["now"]() - this["_t"];
3072 
3073             if(ted >= this["time"]){
3074 
3075                 for(ted in this["origin"]) this["origin"][ted] = this["end"][ted];
3076 
3077                 if(this["onEnd"] !== null){
3078 
3079                     if(this["onEnd"](this) === "restart"){
3080                         if(this["onUpdate"] !== null) this["onUpdate"](this["origin"]);
3081                         this["_start"]();
3082                     }
3083 
3084                     else this["_r"]["stop"](this);
3085                     
3086                 }
3087 
3088                 else this["_r"]["stop"](this);
3089 
3090             }
3091 
3092             else{
3093                 ted = ted / this["time"];
3094                 let n = "";
3095                 for(n in this["origin"]) this["origin"][n] = ted * (this["end"][n] - this["_v"][n]) + this["_v"][n];
3096                 if(this["onUpdate"] !== null) this["onUpdate"](this["origin"]);
3097             }
3098 
3099         }
3100 
3101     }
3102 
3103 }
3104 
3105 
3106 
3107 
3108 /* Tween 影片補間 (TweenValue 的root, 可以管理多個TweenValue)
3109 
3110 parameter:
3111 attribute:
3112 method:
3113     start(value: TweenValue): undefined;
3114     stop(value: TweenValue): undefined;
3115 
3116 static:
3117     Value: TweenValue;
3118 
3119 demo:
3120     //init Tween:
3121     const tween = new Tween(),
3122     animate = function (){
3123         requestAnimationFrame(animate);
3124         tween.update();
3125     }
3126 
3127     //init TweenValue:
3128     const v2 = new Tween.Value({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v), v => {
3129         v2.reverse(); //v2.end 復制起始值
3130         return "restart"; //回傳"restart"表示不洗掉佇列, 需要繼續補間
3131     });
3132     
3133     animate();
3134     tween.start(v2);
3135 
3136 */
3137 class Tween extends RunningList{
3138 
3139     static Value =https://www.cnblogs.com/weihexinCode/p/ TweenValue;
3140 
3141     constructor(){
3142         super();
3143     }
3144 
3145     start(value){
3146         this.add(value);
3147         value._r = this;
3148         value._start();
3149     }
3150 
3151     stop(value){
3152         this.remove(value);
3153         value._r = null;
3154     }
3155 
3156 }
3157 
3158 
3159 
3160 
3161 /* EventDispatcher 自定義事件管理器
3162 parameter: 
3163 attribute: 
3164 
3165 method:
3166     clearEvents(eventName): undefined;             //清除eventName串列, 如果 eventName 未定義清除所有事件
3167     customEvents(eventName, eventParam): this;    //創建自定義事件 eventParam 可選 默認 undefined
3168     getParam(eventName): eventParam;
3169     trigger(eventName, callback): undefined;    //觸發 (callback: 可選)
3170     register(eventName, callback): undefined;    //
3171     deleteEvent(eventName, callback): undefined; //
3172 
3173 demo:
3174     const eventDispatcher = new EventDispatcher();
3175     eventDispatcher.customEvents("test", {name: "test"});
3176 
3177     eventDispatcher.register("test", eventParam => {
3178         console.log(eventParam) //Object{name: "test"}
3179     });
3180 
3181     eventDispatcher.trigger("test");
3182 
3183 */
3184 class EventDispatcher{
3185     
3186     constructor(){
3187         this._eventsList = {};
3188     }
3189 
3190     clearEvents(eventName){ 
3191         if(this._eventsList[eventName] !== undefined) this._eventsList[eventName].func = []
3192         else this._eventsList = {}
3193     }
3194     
3195     customEvents(eventName, eventParam){ 
3196         if(this._eventsList[eventName] !== undefined) return console.warn("EventDispatcher: "+eventName+" 已存在");
3197         this._eventsList[eventName] = {func: [], param: eventParam}
3198         return this;
3199     }
3200 
3201     getParam(eventName){
3202         return this._eventsList[eventName]["param"];
3203     }
3204     
3205     trigger(eventName, callback){
3206         const obj = this._eventsList[eventName];
3207         
3208         if(obj.func.length > 0){
3209             if(typeof callback === "function") callback(obj["param"]);
3210 
3211             const len = obj["func"].length;
3212             for(let k = 0; k < len; k++){
3213                 if(obj["func"][k] !== undefined) obj["func"][k](obj["param"]);
3214             }
3215         }
3216     }
3217     
3218     register(eventName, callback){
3219         if(this._eventsList[eventName] === undefined) return console.warn("EventDispatcher: "+eventName+" 不存在");
3220         const obj = this._eventsList[eventName];
3221         obj.func.push(callback);
3222     }
3223     
3224     deleteEvent(eventName, callback){
3225         if(this._eventsList[eventName] === undefined) return console.warn("EventDispatcher: "+eventName+" 不存在");
3226         const obj = this._eventsList[eventName], 
3227         i = obj.func.indexOf(callback);
3228         if(i !== -1) obj.func.splice(i, 1);
3229     }
3230 
3231 }
3232 
3233 
3234 
3235 
3236 export {
3237     UTILS, 
3238     TweenCache,
3239     AnimateLoop,
3240     Ajax, 
3241     IndexedDB, 
3242     TreeStruct, 
3243     Point, 
3244     Line, 
3245     BoundaryBox,
3246     Box, 
3247     RoundedRectangle,
3248     Circle, 
3249     Polygon, 
3250     Meter,
3251     RGBColor, 
3252     Timer, 
3253     SeekPath, 
3254     RunningList, 
3255     TweenValue, 
3256     Tween, 
3257     TweenTarget, 
3258     EventDispatcher, 
3259     SecurityDoor,
3260 }
Utils.js

 

   1 "use strict";
   2 import {
   3     UTILS, 
   4     Box, 
   5     EventDispatcher,
   6     Point,
   7     AnimateLoop,
   8     TreeStruct,
   9     Timer,
  10     TweenCache,
  11     RGBColor,
  12     Line,
  13     Polygon,
  14     Circle,
  15     RoundedRectangle,
  16     Meter,
  17     SecurityDoor,
  18 } from './Utils.js';
  19 
  20 
  21 /* Touch 事件
  22 touchstart
  23 當用戶在觸摸平面上放置了一個觸點時觸發,事件的目標 element 將是觸點位置上的那個目標 element
  24 
  25 touchend
  26 當一個觸點被用戶從觸摸平面上移除(即用戶的一個手指或手寫筆離開觸摸平面)時觸發,當觸點移出觸摸平面的邊界時也將觸發,例如用戶將手指劃出螢屏邊緣,
  27 事件的目標 element 與觸發 touchstart 事件的目標 element 相同,即使 touchend 事件觸發時,觸點已經移出了該 element ,
  28 已經被從觸摸平面上移除的觸點,可以在 changedTouches 屬性定義的 TouchList 中找到,
  29 
  30 touchmove
  31 當用戶在觸摸平面上移動觸點時觸發,事件的目標 element 和觸發 touchstart 事件的目標 element 相同,即使當 touchmove 事件觸發時,觸點已經移出了該 element ,
  32 當觸點的半徑、旋轉角度以及壓力大小發生變化時,也將觸發此事件,
  33 注意: 不同瀏覽器上 touchmove 事件的觸發頻率并不相同,這個觸發頻率還和硬體設備的性能有關,因此決不能讓程式的運作依賴于某個特定的觸發頻率,
  34 
  35 touchcancel
  36 當觸點由于某些原因被中斷時觸發,有幾種可能的原因如下(具體的原因根據不同的設備和瀏覽器有所不同):
  37 由于某個事件出現而取消了觸摸:例如觸摸程序被彈窗打斷,
  38 觸點離開了檔案視窗,而進入了瀏覽器的界面元素、插件或者其他外部內容區域,
  39 當用戶產生的觸點個數超過了設備支持的個數,從而導致 TouchList 中最早的 Touch 物件被取消,
  40 
  41 
  42 TouchEvent.changedTouches
  43 這個 TouchList 物件列出了和這個觸摸事件對應的 Touch 物件,
  44 對于 touchstart 事件,這個 TouchList 物件列出在此次事件中新增加的觸點,
  45 對于 touchmove 事件,列出和上一次事件相比較,發生了變化的觸點,
  46 對于 touchend 事件,changedTouches 是已經從觸摸面的離開的觸點的集合(也就是說,手指已經離開了螢屏/觸摸面),
  47 
  48 TouchEvent.targetTouches
  49 targetTouches 是一個只讀的 TouchList 串列,包含仍與觸摸面接觸的所有觸摸點的 Touch 物件,touchstart (en-US)事件觸發在哪個element內,它就是當前目標元素,
  50 
  51 TouchEvent.touches
  52 一個 TouchList,其會列出所有當前在與觸摸表面接觸的 Touch 物件,不管觸摸點是否已經改變或其目標元素是在處于 touchstart 階段,
  53 
  54 1 event.changedTouches 上一次的觸點串列 
  55 1 event.targetTouches 某個元素的當前觸點串列 
  56 1 event.touches 螢屏上所有的當前觸點串列 
  57 5 touches: Array[Object{
  58     clientX, clientY
  59     pageX, pageY
  60     screenX, screenY
  61     radiusX, radiusY 
  62     force
  63     identifier
  64     rotationAngle
  65     target
  66 }]
  67 
  68 */
  69 
  70 
  71 function _roundRect(con, x, y, w, h, r){ //con: context || Path2D
  72     const _x = x + r, 
  73     _y = y + r, 
  74     mx = x + w, 
  75     my = y + h, 
  76     _mx = mx - r, 
  77     _my = my - r;
  78 
  79     //
  80     con.moveTo(_x, y);
  81     con.lineTo(_mx, y);
  82     con.arcTo(mx, y, mx, _y, r);
  83 
  84     //
  85     con.lineTo(mx, _y);
  86     con.lineTo(mx, _my);
  87     con.arcTo(mx, my, _x, my, r);
  88 
  89     //
  90     con.lineTo(_x, my);
  91     con.lineTo(_mx, my);
  92     con.arcTo(x, my, x, _my, r);
  93 
  94     //
  95     con.lineTo(x, _y);
  96     con.lineTo(x, _my);
  97     con.arcTo(x, y, _x, y, r);
  98 }
  99 
 100 
 101 const PI2 = Math.PI*2;
 102 
 103 const ElementUtils = {
 104 
 105     getRect(elem){
 106         return elem.getBoundingClientRect();
 107     },
 108 
 109     downloadFile(blob, fileName){
 110         const link = document.createElement("a");
 111         link.href =https://www.cnblogs.com/weihexinCode/p/ URL.createObjectURL(blob);
 112         link.download = fileName;
 113         link.click();
 114     },
 115 
 116     loadFileJSON(onload){
 117         const input = document.createElement("input");
 118         input.type = "file";
 119         input.accept = ".json";
 120         
 121         input.onchange = a => {
 122             if(a.target.files.length === 0) return;
 123             const fr = new FileReader();
 124             fr.onloadend = b => onl oad(b.target.result);
 125             fr.readAsText(a.target.files[0]); //fr.readAsDataURL(a.target.files[0]);
 126         }
 127         
 128         input.click();
 129     },
 130 
 131     loadFileImages(onload){
 132         const input = document.createElement("input");
 133         input.type = "file";
 134         input.multiple = "multiple";
 135         input.accept = ".png, .PNG, .jpg, .JPG, .jpeg, .JPEG, .bmp, .BMP, .gif, .GIF";
 136         
 137         input.onchange = e => {
 138             const files = e.target.files, len = files.length;
 139             if(len === 0) return;
 140 
 141             var i = 0;
 142             const fr = new FileReader(), result = [];
 143             fr.onerror = () => {
 144                 i++; if(i === len && typeof onl oad === "function") onl oad(result);
 145             }
 146             fr.onloadend = ef => {
 147                 if(typeof ef.target.result === "string" && ef.target.result.length > 0) result.push(ef.target.result);
 148                 i++; 
 149                 if(i === len && typeof onl oad === "function") onl oad(result);
 150                 else fr.readAsDataURL(files[i]);
 151             }
 152 
 153             fr.readAsDataURL(files[0]);
 154         }
 155         
 156         input.click();
 157     },
 158 
 159     createCanvas(w = 1, h = 1, className = ""){
 160         const canvas = document.createElement("canvas");
 161         canvas.width = w;
 162         canvas.height = h;
 163         canvas.className = className;
 164         return canvas;
 165     },
 166 
 167     createContext(w = 1, h = 1, alpha = true){
 168         const canvas = document.createElement("canvas"),
 169         context = canvas.getContext("2d", {alpha: alpha});
 170         canvas.width = w;
 171         canvas.height = h;
 172         return context;
 173     },
 174 
 175     //加載圖片并把圖片縮放至 w, h 大小(如果與w,h大小一樣則不縮放直接回傳image而不是canvas);

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/545885.html

標籤:JavaScript

上一篇:初探富文本之CRDT協同實體

下一篇:500行JavaScript代碼在前端根據資料生成CAD工程剖面圖

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 使用Django Rest framework搭建Blog

    在前面的Blog例子中我們使用的是GraphQL, 雖然GraphQL的使用處于上升趨勢,但是Rest API還是使用的更廣泛一些. 所以還是決定回到傳統的rest api framework上來, Django rest framework的官網上給了一個很好用的QuickStart, 我參考Qu ......

    uj5u.com 2023-04-20 08:17:54 more
  • 記錄-new Date() 我忍你很久了!

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 大家平時在開發的時候有沒被new Date()折磨過?就是它的諸多怪異的設定讓你每每用的時候,都可能不小心踩坑。造成程式意外出錯,卻一下子找不到問題出處,那叫一個煩透了…… 下面,我就列舉它的“四宗罪”及應用思考 可惡的四宗罪 1. Sa ......

    uj5u.com 2023-04-20 08:17:47 more
  • 使用Vue.js實作文字跑馬燈效果

    實作文字跑馬燈效果,首先用到 substring()截取 和 setInterval計時器 clearInterval()清除計時器 效果如下: 實作代碼如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta ......

    uj5u.com 2023-04-20 08:12:31 more
  • JavaScript 運算子

    JavaScript 運算子/運算子 在 JavaScript 中,有一些運算子可以使代碼更簡潔、易讀和高效。以下是一些常見的運算子: 1、可選鏈運算子(optional chaining operator) ?.是可選鏈運算子(optional chaining operator)。?. 可選鏈操 ......

    uj5u.com 2023-04-20 08:02:25 more
  • CSS—相對單位rem

    一、概述 rem是一個相對長度單位,它的單位長度取決于根標簽html的字體尺寸。rem即root em的意思,中文翻譯為根em。瀏覽器的文本尺寸一般默認為16px,即默認情況下: 1rem = 16px rem布局原理:根據CSS媒體查詢功能,更改根標簽的字體尺寸,實作rem單位隨螢屏尺寸的變化,如 ......

    uj5u.com 2023-04-20 08:02:21 more
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 08:01:50 more
  • 如何在 vue3 中使用 jsx/tsx?

    我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單檔案組件模式,即一個組件就是一個檔案,但其實 Vue 也是支持使用 JSX 來撰寫組件的。這里不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速了解和使用 Vu ......

    uj5u.com 2023-04-20 08:01:37 more
  • 【Vue2.x原始碼系列06】計算屬性computed原理

    本章目標:計算屬性是如何實作的?計算屬性快取原理以及洋蔥模型的應用?在初始化Vue實體時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ......

    uj5u.com 2023-04-20 08:01:31 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:01:10 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:00:32 more