依賴的類:
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協同實體
