ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

js封装原生画布 Canvas

2022-06-12 18:04:10  阅读:149  来源: 互联网

标签:box Canvas return ca js 画布 ._ null data


   1 "use strict";
   2 
   3 import {UTILS, ColorRefTable, TreeStruct, Box, Circle, Polygon, Point, RGBColor, Timer} from './Utils.js';
   4 
   5 
   6 /* CanvasAnimateEvent (触发过程中可以安全的删除自己)
   7 遇到的坑: 
   8     1: canvas css属性缩放: 使用 .setScale(x, y); x,y: 大于1放大canvas, 小于1缩小canvas
   9     2: canvas 的某个父存在滚动条时需要: scrollElem.onscroll = () => CanvasAnimateRender.updateCanvas();
  10 
  11 parameter:
  12     domElement: CanvasAnimateRender.domElement; //必须
  13     box: CanvasAnimateRender.domElementRect; //必须
  14     ||或者第一个参数为 CanvasAnimateRender
  15 
  16     与.initEvent(domElement, box)参数一样
  17 
  18 attribute:
  19     domElement //CanvasAnimateRender.domElement
  20     box: Box; //忽略box以外的ca, 通常为 CanvasAnimateRender.domElementRect 的引用
  21 
  22 method:
  23     add(ca: CanvasAnimate, eventName: String, callback: Function): ca; //ca添加事件
  24     remove(ca: CanvasAnimate, eventName: String, callback: Function): ca; //ca删除事件
  25         eventName: 可能的值为 CanvasAnimateEvent.canvasEventsList 的属性名
  26         callback: 参数 event, ca
  27     
  28     clear(ca: CanvasAnimate, eventName: String): ca; //ca 必须; eventName 可选, 如果未定义则清空ca的所有事件;
  29     disposeEvent(eventName): this; //.initEvent(domElement, box)调用一次此方法
  30     initEvent(domElement, box): this; //每次更换 domElement, box 后应调用此方法; (CanvasAnimateEvent初始化时自动调用一次)
  31     setScale(x, y: Number): undefiend; //
  32 
  33 event:
  34     (如果注册了 "out"|"over" 事件, 在弃用时(CanvasAnimateRender 不在使用): 
  35     必需要调用.disposeEvent(eventName)方法清理注册的dom事件, 
  36     因为这两个事件用的是 pointermove 而不是 onpointermove);
  37 
  38     CanvasAnimateEvent.canvasEventsList
  39 
  40 demo:
  41     const car = new CanvasAnimateRender({width: 100, height: 100}),
  42     cae = new CanvasAnimateEvent(car),
  43     ca = car.add(new CanvasAnimate(image));
  44 
  45     //ca添加点击事件
  46     cae.add(ca, 'click', (event, target) => console.log(event, target));
  47 
  48     car.render();
  49 
  50 */
  51 class CanvasAnimateEvent{
  52 
  53     static bind(obj, is){
  54         obj._eventList = {}
  55 
  56         if(is === true){
  57             let k, evns = CanvasAnimateEvent.canvasEventsList;
  58             for(k in evns) obj._eventList[k] = [];
  59         }
  60             
  61     }
  62 
  63     static canvasEventsList = {
  64         down: "onpointerdown",
  65         move: "onpointermove",
  66         up: "onpointerup",
  67         click: "onclick",
  68         wheel: "onmousewheel",
  69         out: "pointermove", //移出 
  70         over: "pointermove", //移入
  71 
  72     }
  73     
  74     constructor(domElement, box){
  75         this._box = new Box();
  76         this._scale = {x: 1, y: 1};
  77         this._running = "";
  78         this._delList = [];
  79         this.initEvent(domElement, box);
  80         CanvasAnimateEvent.bind(this);
  81 
  82     }
  83 
  84     setScale(x, y){
  85         this._scale.x = x || 1;
  86         this._scale.y = y || 1;
  87 
  88         if(this.domElement){
  89             this.domElement.style.width = this.domElement.width * this._scale.x + "px";
  90             this.domElement.style.height = this.domElement.height * this._scale.y + "px";
  91         }
  92 
  93     }
  94 
  95     initEvent(domElement, box){
  96         this.disposeEvent();
  97 
  98         if(CanvasAnimateRender.prototype.isPrototypeOf(domElement)){
  99             this.domElement = domElement.domElement;
 100             this.box = domElement.domElementRect;
 101         }
 102 
 103         else{
 104             this.domElement = domElement;
 105             this.box = box;
 106         }
 107 
 108         if(this._eventList !== undefined){
 109             for(let evn in this._eventList){
 110                 if(this._eventList[evn] !== undefined) this._createEvent(evn);
 111             }
 112 
 113         }
 114         
 115         return this;
 116     }
 117 
 118     add(ca, eventName, callback){
 119         if(CanvasAnimateEvent.canvasEventsList[eventName] === undefined) return console.warn("CanvasAnimateEvent: 参数错误 "+ eventName);
 120         if(typeof callback !== "function") return console.warn("CanvasAnimateEvent: 事件添加失败,参数错误");
 121         
 122         this._add(ca, eventName);
 123         this._addCA(ca, eventName, callback);
 124         
 125         return ca;
 126     }
 127     
 128     remove(ca, eventName, callback){
 129         if(CanvasAnimateEvent.canvasEventsList[eventName] === undefined) return console.warn("CanvasAnimateEvent: 参数错误 "+ eventName);
 130         if(typeof callback !== "function") return console.warn("CanvasAnimateEvent: 事件添加失败,参数错误");
 131         if(this._running !== eventName){
 132             this._remove(ca, eventName);
 133             this._removeCA(ca, eventName, callback);
 134         }
 135 
 136         else this._delList.push(ca, eventName, callback);
 137 
 138         return ca;
 139     }
 140 
 141     disposeEvent(eventName){
 142         if(!this.domElement) return this;
 143         
 144         if(eventName === "over" || eventName === "out"){
 145 
 146             if(typeof this["_"+eventName] === "function"){
 147                 this.domElement.removeEventListener(CanvasAnimateEvent.canvasEventsList[eventName], this["_"+eventName]);
 148                 delete this["_"+eventName];
 149             }
 150 
 151         }
 152 
 153         else{
 154 
 155             if(typeof this["_over"] === "function"){
 156                 this.domElement.removeEventListener(CanvasAnimateEvent.canvasEventsList["over"], this["_over"]);
 157                 delete this["_over"];
 158             }
 159 
 160             if(typeof this["_out"] === "function"){
 161                 this.domElement.removeEventListener(CanvasAnimateEvent.canvasEventsList["out"], this["_out"]);
 162                 delete this["_out"];
 163             }
 164 
 165         }
 166 
 167         return this;
 168     }
 169     
 170     clear(ca, eventName){
 171         if(eventName === undefined){
 172             var k; for(k in this._eventList){
 173                 this._remove(ca, k);
 174             }
 175             
 176             if(ca._eventList !== undefined) delete ca._eventList; //CanvasAnimateEvent.bind(ca, true);
 177             
 178         }
 179 
 180         else if(CanvasAnimateEvent.canvasEventsList[eventName] !== undefined){
 181             this._remove(ca, eventName);
 182 
 183             if(ca._eventList !== undefined) ca._eventList[eventName].length = 0;
 184             
 185         }
 186 
 187         return ca;
 188     }
 189 
 190     _addCA(ca, eventName, callback){
 191         if(ca._eventList === undefined) CanvasAnimateEvent.bind(ca);
 192         if(ca._eventList[eventName] === undefined) ca._eventList[eventName] = [];
 193         ca._eventList[eventName].push(callback);
 194 
 195     }
 196 
 197     _removeCA(ca, eventName, callback){
 198         if(ca._eventList !== undefined && ca._eventList[eventName] !== undefined){
 199             for(let k = 0, len = ca._eventList[eventName].length; k < len; k++){
 200                 if(ca._eventList[eventName][k] === callback){
 201                     ca._eventList[eventName].splice(k, 1);
 202                     break;
 203                 }
 204             }
 205         }
 206 
 207     }
 208 
 209     _add(ca, eventName){
 210         if(this._eventList[eventName] === undefined){
 211             this._eventList[eventName] = [];
 212             this._createEvent(eventName);
 213 
 214         }
 215 
 216         if(this._eventList[eventName].includes(ca) === false) this._eventList[eventName].push(ca);
 217 
 218     }
 219 
 220     _remove(ca, eventName){
 221         if(this._eventList[eventName] !== undefined){
 222             let key = this._eventList[eventName].indexOf(ca);
 223             if(key !== -1) this._eventList[eventName].splice(key, 1);
 224             if(key === 0){
 225                 if(eventName == "over" || eventName === "out") this.disposeEvent(eventName);
 226                 else this.domElement[CanvasAnimateEvent.canvasEventsList[eventName]] = null;
 227                 delete this._eventList[eventName];
 228             }
 229 
 230         }
 231 
 232     }
 233 
 234     _createEvent(evn){
 235         var k, len, ca, arr, tar = null, oldTar = null, _run = null, _box = this._box, _scale = this._scale;
 236 
 237         const run = event => {
 238             len = this["_eventList"][evn].length;
 239             _box.set(this.box.x, this.box.y, this.box.w * _scale.x, this.box.h * _scale.y);
 240             
 241             if(len !== 0 && _box["containsPoint"](event["pageX"], event["pageY"]) === true){
 242                 tar = null;
 243                 for(k = 0; k < len; k++){
 244                     ca = this["_eventList"][evn][k];
 245                     _box.set(ca["box"]["x"] * _scale["x"], ca["box"]["y"] * _scale["y"], ca["box"]["w"] * _scale["x"], ca["box"]["h"] * _scale["y"]);
 246                     
 247                     if(ca["visible"] === true && _box["containsPoint"](event["pageX"] - this["box"]["x"], event["pageY"] - this["box"]["y"]) === true){
 248                         
 249                         if(tar === null || tar["index"] < ca["index"]) tar = ca;
 250                         
 251                     }
 252     
 253                 }
 254                 
 255                 if(_run !== null) _run();
 256                 if(tar !== null){
 257                     this._running = evn;
 258                     arr = tar["_eventList"][evn]; 
 259                     len = arr.length;
 260                     for(k = 0; k < len; k++) arr[k](event, tar);
 261                     tar = null;
 262 
 263                     len = this._delList.length;
 264                     for(k = 0; k < len; k += 3){
 265                         this._remove(this._delList[k], this._delList[k+1]);
 266                         this._removeCA(this._delList[k], this._delList[k+1], this._delList[k+2]);
 267                     }
 268                     this._running = "";
 269                     this._delList.length = 0;
 270                 }
 271 
 272             }
 273             
 274         }
 275 
 276         if(evn == "over" || evn === "out"){
 277             this.domElement.addEventListener(CanvasAnimateEvent.canvasEventsList[evn], run);
 278             this["_"+evn] = run;
 279             if(evn === "over"){
 280                 _run = ()=>{
 281                     if(tar !== null){
 282                         if(oldTar !== null){
 283                             if(oldTar !== tar) oldTar = tar;
 284                             else tar = null;
 285                         }
 286                         else oldTar = tar;
 287                     }
 288                     else if(oldTar !== null) oldTar = null;
 289     
 290                 }
 291 
 292             }
 293 
 294             else{
 295                 let _tar = null;
 296                 _run = ()=>{
 297                     if(tar !== null){
 298                         if(oldTar !== null){
 299                             if(oldTar !== tar){
 300                                 _tar = tar;
 301                                 tar = oldTar;
 302                                 oldTar = _tar;
 303                             }
 304                             else tar = null;
 305                         }
 306                         else{
 307                             oldTar = tar;
 308                             tar = null;
 309                         }
 310                     }
 311                     else if(oldTar !== null){
 312                         tar = oldTar;
 313                         oldTar = null;
 314                     }
 315     
 316                 }
 317 
 318             }
 319 
 320             /* _run = ()=>{
 321                 if(tar !== null){
 322                     if(oldTar !== null){
 323 
 324                         if(oldTar !== tar){
 325                             if(evn === "over") oldTar = tar;
 326                             else{
 327                                 let _tar = tar;
 328                                 tar = oldTar;
 329                                 oldTar = _tar;
 330                             }
 331                         }
 332 
 333                         else tar = null;
 334 
 335                     }
 336 
 337                     else{
 338                         oldTar = tar;
 339                         if(evn === "out") tar = null;
 340                         
 341                     }
 342                     
 343                 }
 344 
 345                 else{
 346                     if(oldTar !== null){
 347                         if(evn === "out") tar = oldTar;
 348                         oldTar = null;
 349                     }
 350                     
 351                 }
 352 
 353             } */
 354             
 355         }
 356 
 357         else this.domElement[CanvasAnimateEvent.canvasEventsList[evn]] = run;
 358 
 359     }
 360 
 361 }
 362 
 363 
 364 
 365 
 366 /* CanvasAnimateRender (渲染 CanvasAnimate)
 367 注意:
 368     this.box = new Box();                 //本类绘制时用这box检测 (ca的box是否与这个box相交, 如果相交才有可能绘制这个ca)
 369     this.domElementRect = new Box();     //CanvasAnimateEvent 用这个box检测 (鼠标的位置是否在这个box范围内, 如果在才有可能触发这个ca的事件)
 370 
 371 parameter: 
 372     option = {
 373         canvas //默认新的canvas
 374         width, height //默认1
 375         className //canvas的 css 类名 默认 ""
 376         id //
 377     }
 378 
 379 attribute:
 380     list: Array[CanvasAnimate]; //渲染队列
 381     box: Box; //canvas的位置和范围(.x.y是0; .w.h是canvas的宽高, 在渲染时检测ca的box是否与此box相交);
 382     context: CanvasContext;
 383     domElement: Canvas;
 384 
 385 method:
 386     isDraw(ca): Bool; //ca是否满足绘制条件
 387     isCanvasImage(img: Object): Bool; //img是否是canvas可绘制的图片;
 388     add(ca): ca; //添加ca (添加多个: const len = this.list.length; this.list.push(caA, caB); this.initList(len))
 389     remove(ca): ca; //删除ca (删除多个: this.list.splice(i, len); this.initList(i));
 390     updateCanvas(): this; //更新canvas的box, canvas添加到dom树后才有效; (如果你没有用.pos(x, y)和.size(w, h, setElem)设置canvas的位置和宽高的话, 那么你需要调用一次此方法, 否则 事件错误 等异常)
 391     initList(index): this; //初始化ca列表; (如果你没有用.add(ca)添加 或 .remove(cd)删除 那么你需要调用一次此方法, 否则 删除错误, 事件错误 等异常)
 392     pos(x, y): this; //设置canvas的位置和this.box的位置
 393     size(w, h: Number, setElem: Bool): this; //设置this.box的宽高; setElem: 是否同时设置canvas的宽高; 默认true
 394     render(parentElem): this; //绘制一次画布, 并把画布添加到parentElem, parentElem 默认 body, 然后调用一次 this.updateCanvas() 方法;
 395     clear(): undefiend; //清除画布
 396     draw(): undefiend; //绘制画布
 397     redraw(): this; //清除并绘制画布; 一般在动画循环里面调用此方法
 398     clearTarget(ca): undefiend; //清除 ca; 适应静态视图
 399     drawTarget(ca): undefiend; //绘制 ca; 适应静态视图 
 400     computeOverlaps(ca)
 401 
 402     //以下方法即将弃用
 403     shear(ca, canvas, x, y): canvas; //this.canvas 的 ca 范围剪切到 canvas 的 x,y位置; ca默认this; canvas模型新的canvas; x, y默认0;
 404     getData(box)
 405     putData(data, x, y)
 406 
 407     //参数名:
 408     ca: CanvasAnimate; 包括它的子类
 409     box: Box;
 410 
 411 demo:
 412 
 413     //更新ca.box(注意执行顺序, 这很重要):
 414     root.clearTarget(ca);
 415     ca.box.set(10, 50, 100, 100);
 416     root.drawTarget(ca);
 417 
 418     //显示:
 419     ca.visible = false;
 420     root.clearTarget(ca);
 421 
 422     ca.visible = true;
 423     root.drawTarget(ca);
 424 
 425     //或者定义一个空的ca:
 426     const emptyCA = new CanvasAnimate();
 427     ca.visible = true;
 428     emptyCA.box.copy(ca.box);
 429     root.drawTarget(ca);
 430 
 431 以以上方法绘制ca的利与弊:
 432     利: 比.redraw()效率更高; .redraw()每次绘制全部可绘制的ca, 而此方法每次只绘制与ca.box重叠的ca数;
 433     弊: 如果目标ca的box覆盖了整个canvas那么像上面这样绘制反而会比.redraw()慢;
 434 
 435 更多例子: class CanvasAnimateUI 使用此方法更新画布
 436 
 437 */
 438 class CanvasAnimateRender{
 439 
 440     constructor(option = {}){
 441         this.list = [];
 442         this.box = new Box();
 443         this.domElementRect = new Box();
 444         this.context = CanvasAnimateRender.getContext(option.canvas, option.className, option.id);
 445         this.domElement = this.context.canvas;
 446         
 447         //init
 448         //this.size(option.width, option.height)
 449         if(option.width !== undefined) this.domElement.width = option.width;
 450         if(option.height !== undefined) this.domElement.height = option.height;
 451         
 452         this.box.size(this.domElement.width, this.domElement.height);
 453         this.domElementRect.size(this.box.w, this.box.h);
 454 
 455         //this.domElement.style.position = "absolute";
 456         //this.domElement.style.background = "rgb(127,127,127)";
 457         CanvasAnimateRender.setDefaultStyles(this.context);
 458         
 459     }
 460 
 461     isDraw(ca){
 462 
 463         return CanvasAnimateRender.isCA(ca) && ca["visible"] === true && CanvasAnimateRender.isCanvasImage(ca["image"]) && this["box"]["intersectsBox"](ca["box"]);
 464 
 465     }
 466 
 467     isCA(ca){
 468         
 469         return CanvasAnimateRender.isCA(ca);
 470 
 471     }
 472 
 473     isCanvasImage(img){
 474 
 475         return CanvasAnimateRender.isCanvasImage(img);
 476 
 477     }
 478 
 479     updateCanvas(){
 480         //this.box.size(this.domElement.width, this.domElement.height);
 481         const rect = this.domElement.getBoundingClientRect();
 482         if(rect.width !== 0) this.domElementRect.set(rect.x, rect.y, this.box.w, this.box.h);
 483         
 484         return this;
 485     }
 486 
 487     getImageData(box = this.box){
 488         
 489         return this.context.getImageData(box.x, box.y, box.w, box.h);
 490 
 491     }
 492 
 493     putImageData(data, x, y){
 494 
 495         return this.context.putImageData(data, x, y);
 496 
 497     }
 498 
 499     pos(x, y){
 500         this.domElement.style.position = "absolute";
 501         this.domElement.style.left = x + "px";
 502         this.domElement.style.top = y + "px";
 503 
 504         //this.box.pos(0, 0);
 505         const rect = this.domElement.getBoundingClientRect();
 506         this.domElementRect.pos(rect.x, rect.y);
 507 
 508         return this;
 509     }
 510 
 511     size(w, h, setElem){
 512         //重置canvas的宽高时; 系统会自动将其恢复默认状态(其属性); 还会清理整个画布;
 513 
 514         if(typeof w !== "number" || w < 1) w = 1;
 515         if(typeof h !== "number" || h < 1) h = 1;
 516         //setElem = typeof setElem === "boolean" ? setElem : true;
 517 
 518         if(setElem !== false){
 519             this.domElement.width = w;
 520             this.domElement.height = h;
 521             CanvasAnimateRender.setDefaultStyles(this.context);
 522         }
 523 
 524         this.box.size(w, h);
 525         this.domElementRect.size(w, h);
 526         
 527         return this;
 528     }
 529 
 530     add(ca){
 531         if(this.list.includes(ca) === false){
 532             const len = this.list.length;
 533             
 534             if(this.list[ca.index] === undefined){
 535                 ca.index = len;
 536                 this.list.push(ca);
 537 
 538             }
 539 
 540             else{
 541                 const arr = this.list.splice(ca.index);
 542                 this.list.push(ca);
 543                 for(let k = 0, c = arr.length; k < c; k++){
 544                     this.list.push(arr[k]);
 545                     arr[k].index++;
 546                 }
 547 
 548             }
 549 
 550         }
 551         
 552         return ca;
 553     }
 554 
 555     remove(ca){
 556         var i = ca.index;
 557 
 558         if(this.list[i] !== ca) i = this.list.indexOf(ca);
 559 
 560         if(i !== -1){
 561             this.list.splice(i, 1);
 562             for(let k = i, len = this.list.length; k < len; k++) this.list[k].index -= 1;
 563 
 564         }
 565 
 566         return ca;
 567     }
 568 
 569     render(parentElem){
 570         parentElem = parentElem || document.body;
 571         parentElem.appendChild(this.domElement);
 572         this.updateCanvas();
 573         this.redraw();
 574         return this;
 575     }
 576 
 577     initList(i){
 578         if(i === undefined || i < 0) i = 0;
 579         for(let k = i, len = this.list.length; k < len; k++) this.list[k].index = k;
 580         return this;
 581     }
 582 
 583     clear(){
 584 
 585         this['context']['clearRect'](0, 0, this['box']['w'], this['box']['h']);
 586 
 587     }
 588 
 589     _draw(ca){
 590         if(ca["opacity"] === 1) this["context"]["drawImage"](ca["image"], ca["box"]["x"], ca["box"]["y"], ca["box"]["w"], ca["box"]["h"]);
 591 
 592         else if(ca["opacity"] > 0){
 593             this["context"]["globalAlpha"] = ca["opacity"];
 594             this["context"]["drawImage"](ca["image"], ca["box"]["x"], ca["box"]["y"], ca["box"]["w"], ca["box"]["h"]);
 595             this["context"]["globalAlpha"] = 1;
 596         }
 597 
 598     }
 599 
 600     draw(){
 601         const len = this["list"]["length"];
 602         for(let k = 0, ca; k < len; k++){
 603             ca = this["list"][k];
 604             if(this["isDraw"](ca) === true) this["_draw"](ca);
 605         }
 606         
 607     }
 608 
 609     _drawTarget(ca){
 610         const len = this["list"]["length"];
 611         this["context"]["clearRect"](ca["overlap"]["box"]["x"], ca["overlap"]["box"]["y"], ca["overlap"]["box"]["w"], ca["overlap"]["box"]["h"]);
 612 
 613         for(let k = 0; k < len; k++){
 614             ca = this["list"][k];
 615             if(ca["overlap"]["draw"] === true) this._draw(ca);
 616             
 617         }
 618 
 619     }
 620 
 621     computeOverlap(tar){
 622         //1 如果检索到与box相交的ca时, 合并其box执行以下步骤: 
 623         //2 检索 已检索过的 并且 没有相交的ca
 624         //3 如果存在相交的ca就合并box并继续第 2 步; 如果不存在继续第 1 步
 625         if(tar["overlap"] === null) tar["overlap"] = {box: new Box(), draw: false};
 626         const _list = CanvasAnimateRender.emptyArrA, list = CanvasAnimateRender.emptyArrB, len = this.list.length, box = tar["overlap"]["box"]["copy"](tar["box"]);
 627 
 628         for(let k = 0, i = 0, c = 0, _c = 0, a = _list, b = list, loop = false; k < len; k++){
 629             tar = this["list"][k];
 630             if(tar["overlap"] === null) tar["overlap"] = {box: new Box(), draw: false};
 631             
 632             if(this.isDraw(tar) === false){
 633                 if(tar["overlap"]["draw"] !== false) tar["overlap"]["draw"] = false;
 634                 continue;
 635             }
 636 
 637             if(box["intersectsBox"](tar["box"]) === true){
 638                 if(tar["overlap"]["draw"] !== true) tar["overlap"]["draw"] = true;
 639                 box["expand"](tar["box"]);
 640                 loop = true;
 641 
 642                 while(loop === true){
 643                     b["length"] = 0;
 644                     loop = false;
 645                     c = _c;
 646 
 647                     for(i = 0; i < c; i++){
 648                         tar = a[i];
 649 
 650                         if(box["intersectsBox"](tar["box"]) === true){
 651                             if(tar["overlap"]["draw"] !== true) tar["overlap"]["draw"] = true;
 652                             box["expand"](tar["box"]);
 653                             loop = true; _c--;
 654                         }
 655 
 656                         else b.push(tar);
 657                         
 658                     }
 659 
 660                     a = a === _list ? list : _list;
 661                     b = b === _list ? list : _list;
 662 
 663                 }
 664                 
 665             }
 666 
 667             else{
 668                 _c++;
 669                 a["push"](tar);
 670                 if(tar["overlap"]["draw"] !== false) tar["overlap"]["draw"] = false;
 671             }
 672 
 673         }
 674         
 675         _list.length = list.length = 0;
 676 
 677     }
 678 
 679     clearTarget(ca){
 680         this["computeOverlap"](ca);
 681         ca["overlap"]["draw"] = false;
 682         this["_drawTarget"](ca);
 683     
 684     }
 685 
 686     drawTarget(ca){
 687         this["computeOverlap"](ca);
 688         this["_drawTarget"](ca);
 689 
 690     }
 691 
 692     redraw(){
 693         this.clear();
 694         this.draw();
 695 
 696     }
 697 
 698     static emptyArrA = []
 699     static emptyArrB = []
 700     static paramCon = {alpha: true}
 701 
 702     static defaultStyles = {
 703         filter: "none",
 704         globalAlpha: 1,
 705         globalCompositeOperation: "source-over",
 706         imageSmoothingEnabled: true,
 707         miterLimit: 10,
 708         font: "12px SimSun, Songti SC",
 709         textAlign: "left",
 710         textBaseline: "top",
 711         lineCap: "butt",
 712         lineJoin: "miter",
 713         lineDashOffset: 0,
 714         lineWidth: 1,
 715         shadowColor: "rgba(0, 0, 0, 0)",
 716         shadowBlur: 0,
 717         shadowOffsetX: 0,
 718         shadowOffsetY: 0,
 719         fillStyle: "#000000",
 720         strokeStyle: "#ffffff",
 721     }
 722 
 723     static setDefaultStyles(context){
 724         let k = "", styles = CanvasAnimateRender.defaultStyles;
 725         for(k in styles){
 726             if(context[k] !== styles[k]) context[k] = styles[k];
 727         }
 728 
 729     }
 730 
 731     static getContext(canvas, className, id){
 732         if(CanvasAnimateRender.isCanvas(canvas) === false) canvas = document.createElement("canvas");
 733         const context = canvas.getContext("2d", CanvasAnimateRender.paramCon);
 734 
 735         if(typeof className === "string") canvas.className = className;
 736         if(typeof id === "string") canvas.setAttribute('id', id);
 737         
 738         return context;
 739     }
 740 
 741     static downloadImage(func){
 742         const input = document.createElement("input");
 743         input.type = "file";
 744         input.multiple = "multiple";
 745         input.accept = ".png, .jpg, .jpeg, .bmp, .gif";
 746     
 747         input.onchange = e => {
 748             if(e.target.files.length === 0) return;
 749             const fr = new FileReader();
 750             fr.onloadend = e1 => {
 751                 const img = new Image();
 752                 img.onload = () => func(img);
 753                 img.src = e1.target.result;
 754             }
 755 
 756             fr.readAsDataURL(e.target.files[0]);
 757         }
 758         
 759         input.click();
 760     }
 761 
 762     static isCA(ca){
 763         
 764         return UTILS.isObject(ca) && ca.isCanvasAnimate;
 765 
 766     }
 767 
 768     static isCanvasImage(img){ //OffscreenCanvas:ImageBitmap; CSSImageValue:
 769 
 770         return UTILS.isObject(img) && (ImageBitmap["prototype"]["isPrototypeOf"](img) || HTMLImageElement["prototype"]["isPrototypeOf"](img) || HTMLCanvasElement["prototype"]["isPrototypeOf"](img) || CanvasRenderingContext2D["prototype"]["isPrototypeOf"](img) || HTMLVideoElement["prototype"]["isPrototypeOf"](img));
 771         
 772     }
 773 
 774     static isCanvas(canvas){
 775         
 776         return UTILS.isObject(canvas) && HTMLCanvasElement["prototype"]["isPrototypeOf"](canvas);
 777 
 778     }
 779 
 780     static gradientColor(gradient, colors, close){
 781         if(Array.isArray(colors) === true){
 782             const len = colors.length;
 783             for(let k = 0; k < len; k++) gradient.addColorStop(k / len, colors[k]);
 784             if(close === true) gradient.addColorStop(1, colors[0]);
 785         }
 786 
 787     }
 788 
 789     static gradientColorSymme(gradient, colors){ //长度为2: 0: colors[0] , 0.5: colors[1], 1: colors[0]; (如果长度为2除外的偶数则忽略最后一个颜色)
 790         if(Array.isArray(colors) === true){
 791             const len = Math.round(colors.length/2), count = len * 2;
 792             
 793             for(let k = 0; k < len; k++){
 794                 gradient.addColorStop(k / count, colors[k]);
 795             }
 796 
 797             for(let k = len, i = len; k >= 0; k--, i++){
 798                 gradient.addColorStop(i / count, colors[k]);
 799             }
 800             
 801         }
 802 
 803     }
 804 
 805 }
 806 
 807 
 808 
 809 
 810 /* CanvasAnimateRenderScroll 内置了滚动条的 功能和视图
 811 
 812 parameter:
 813     option: Object{
 814         scrollWidth: Number, //滚动条宽 
 815         scrollColor: String, //滚动条背景色
 816         cursorColor: String, //滚动条游标颜色
 817     }
 818 
 819 attribute:
 820     onscroll: Function(pointer event, scroll type);    //用于监听scroll 默认 null
 821 
 822     //以下属性不建议修改
 823     eventDispatcher: CanvasAnimateEvent;
 824     scrollX: CanvasAnimateCustom;
 825     scrollY: CanvasAnimateCustom;
 826     cursorX: CanvasAnimateCustom;
 827     cursorY: CanvasAnimateCustom;
 828 
 829 method:
 830     addEvent(ca, eventName, callback)
 831     removeEvent(ca, eventName, callback)
 832     getScrollLeft(): Number; //返回滚动轴左边的距离
 833     getScrollTop(): Number; //返回滚动轴上边的距离
 834 
 835     setScroll(type, v, offset): this;
 836         type: "top|left"; //滚动条的类型
 837         v: Number|CanvasAnimate;
 838         offset: Number; //偏移值; 默认: 0;
 839     
 840     setScrollWidth(v): this; 
 841         v: Number; //设置滚动条的宽; v不能小于1
 842     
 843 demo:
 844     
 845     const cars = new CanvasAnimateRenderScroll({
 846         width: 300, 
 847         height: 300,
 848         scrollWidth: 10,
 849     }).pos(50, 50),
 850 
 851     cac = cars.add(new CanvasAnimateCustom()).size(150, 150).rect(4).fill("#fff").stroke("blue", 1).pos(40, 40),
 852     cac1 = cars.add(new CanvasAnimate(cac.image)).pos(40, 230),
 853     cac2 = cars.add(new CanvasAnimate(cac.image)).pos(40, 900),
 854     cac3 = cars.add(new CanvasAnimate(cac.image)).pos(900, 66);
 855 
 856     cars.domElement.style = `
 857         position: absolute;
 858         z-index: 999;
 859         top: 50px;
 860         left: 100px;
 861         background: #fff;
 862     `;
 863     
 864     //自定义 scroll style
 865     cars.scrollY.clear().rect().shadow("#ddd", 1, -1, 0).fill("#eee");
 866 
 867     cars.render();
 868     cars.addEvent(cac, "click", () => {
 869         cars.setScroll("top", cac1, -10).redraw();
 870         console.log(cars.getScrollTop()); //220
 871     });
 872 
 873 */
 874 class CanvasAnimateRenderScroll extends CanvasAnimateRender{
 875 
 876     #maxX = 0;
 877     #maxY = 0;
 878     #scrollWidth = 10;
 879     #scrollColor = "#eee";
 880     #cursorColor = "#aaa";
 881 
 882     constructor(option = {}){
 883         super(option);
 884 
 885         if(typeof option.scrollWidth === "number") this.#scrollWidth = option.scrollWidth;
 886         if(option.scrollColor) this.#scrollColor = option.scrollColor;
 887         if(option.cursorColor) this.#cursorColor = option.cursorColor;
 888 
 889         this.eventDispatcher = new CanvasAnimateEvent(this);
 890         this.scrollX = new CanvasAnimateCustom();
 891         this.scrollY = new CanvasAnimateCustom();
 892         this.cursorX = this._bindScroll(new CanvasAnimateCustom());
 893         this.cursorY = this._bindScroll(new CanvasAnimateCustom());
 894         this.wheelCA = new CanvasAnimate();
 895         this.onscroll = null;
 896         
 897         this.cursorBoxX = this.cursorX.box.set(0, this.box.h - this.#scrollWidth, this.box.w - this.#scrollWidth, this.#scrollWidth);
 898         this.cursorBoxY = this.cursorY.box.set(this.box.w - this.#scrollWidth, 0, this.#scrollWidth, this.box.h - this.#scrollWidth);
 899 
 900         this.scrollX.size(this.cursorBoxX.w, this.cursorBoxX.h).rect().fill(this.#scrollColor).box.pos(0, this.cursorBoxX.y);
 901         this.scrollY.size(this.cursorBoxY.w, this.cursorBoxY.h).rect().fill(this.#scrollColor).box.pos(this.cursorBoxY.x, 0);
 902 
 903         //init scroll event
 904         this.scrollX.index = 
 905         this.scrollY.index = 99999;
 906 
 907         this.cursorX.index = 
 908         this.cursorY.index = Infinity;
 909 
 910         var dPos = 0;
 911 
 912         const setTop = (event, top) => {
 913             if(this.setScrollTop(top) === true){
 914                 if(this.onscroll !== null) this.onscroll(event, "y");
 915                 this.redraw();
 916             }
 917 
 918         },
 919 
 920         setLeft = (event, left) => {
 921             if(this.setScrollLeft(left) === true){
 922                 if(this.onscroll !== null) this.onscroll(event, "x");
 923                 this.redraw();
 924             }
 925 
 926         },
 927         
 928         onMoveTop = event => {
 929             setTop(event, event.pageY - this.domElementRect.y - dPos);
 930 
 931         },
 932 
 933         onMoveLeft = event => {
 934             setLeft(event, event.pageX - this.domElementRect.x - dPos);
 935 
 936         },
 937 
 938         onUpTop = event => {
 939             document.body.removeEventListener('pointermove', onMoveTop);
 940             document.body.removeEventListener('pointerup', onUpTop);
 941 
 942             this.domElement.removeEventListener('pointermove', onMoveTop);
 943             this.domElement.removeEventListener('pointerup', onUpTop);
 944 
 945             onMoveTop(event);
 946 
 947         },
 948 
 949         onUpLeft = event => {
 950             document.body.removeEventListener('pointermove', onMoveLeft);
 951             document.body.removeEventListener('pointerup', onUpLeft);
 952 
 953             this.domElement.removeEventListener('pointermove', onMoveLeft);
 954             this.domElement.removeEventListener('pointerup', onUpLeft);
 955 
 956             onMoveLeft(event);
 957 
 958         }
 959 
 960         this.eventDispatcher.add(this.cursorY, "down", event => {
 961             dPos = event.pageY - this.domElementRect.y - this.cursorBoxY.y;
 962             onUpTop(event);
 963 
 964             document.body.addEventListener("pointermove", onMoveTop);
 965             document.body.addEventListener("pointerup", onUpTop);
 966 
 967             this.domElement.addEventListener("pointermove", onMoveTop);
 968             this.domElement.addEventListener("pointerup", onUpTop);
 969 
 970         });
 971 
 972         this.eventDispatcher.add(this.cursorX, "down", event => {
 973             dPos = event.pageX - this.domElementRect.x - this.cursorBoxX.x;
 974             onUpLeft(event);
 975 
 976             document.body.addEventListener("pointermove", onMoveLeft);
 977             document.body.addEventListener("pointerup", onUpLeft);
 978 
 979             this.domElement.addEventListener("pointermove", onMoveLeft);
 980             this.domElement.addEventListener("pointerup", onUpLeft);
 981 
 982         });
 983 
 984         this.eventDispatcher.add(this.scrollY, "down", event => {
 985             dPos = this.cursorBoxY.h / 2;
 986             onUpTop(event);
 987             
 988         });
 989 
 990         this.eventDispatcher.add(this.scrollX, "down", event => {
 991             dPos = this.cursorBoxX.w / 2;
 992             onUpLeft(event);
 993             
 994         });
 995 
 996         this.wheelCA.box.copy(this.box);
 997         this.eventDispatcher.add(this.wheelCA, "wheel", event => {
 998             dPos = event.wheelDelta === 120 ? -1 : 1; //(0 - event.wheelDelta) * 0.1;
 999             if(this.#maxY > this.box.h && this.cursorY.visible === true) setTop(event, this.cursorBoxY.y + dPos * this.box.h / 10);
1000             else if(this.#maxX > this.box.w && this.cursorX.visible === true) setLeft(event, this.cursorBoxX.x + dPos * this.box.w / 10);
1001             
1002         });
1003 
1004     }
1005 
1006     size(w, h, setElem){
1007         super.size(w, h, setElem);
1008         this.setScrollWidth(this.#scrollWidth);
1009         this.wheelCA.box.copy(this.box);
1010 
1011         return this;
1012     }
1013 
1014     add(ca){
1015         this._bind(ca);
1016         return super.add(ca);
1017     }
1018 
1019     remove(ca){
1020         this._unBind(ca);
1021         return super.remove(ca);
1022     }
1023 
1024     initList(i){
1025         if(i === undefined || i < 0) i = 0;
1026         for(let k = i, len = this.list.length; k < len; k++) this._bind(this.list[k]);
1027 
1028         return super.initList(i);
1029     }
1030 
1031     draw(){
1032         super.draw();
1033         this._drawScroll();
1034 
1035     }
1036 
1037     _drawTarget(ca){
1038         super._drawTarget(ca);
1039         this._drawScroll();
1040 
1041     }
1042 
1043 
1044     //new method
1045     addEvent(ca, eventName, callback){
1046 
1047         return this.eventDispatcher.add(ca, eventName, callback);
1048         
1049     }
1050 
1051     removeEvent(ca, eventName, callback){
1052 
1053         return this.eventDispatcher.remove(ca, eventName, callback);
1054 
1055     }
1056 
1057     //获取scroll的场景位置
1058     getScrollLeft(){
1059         if(this.#maxX <= this.box.w) return 0;
1060         return this.cursorBoxX.x / (this.box.w - this.cursorBoxY.w) * this.#maxX;
1061     }
1062 
1063     getScrollTop(){
1064         if(this.#maxY <= this.box.h) return 0;
1065         return this.cursorBoxY.y / (this.box.h - this.cursorBoxX.h) * this.#maxY;
1066     }
1067 
1068     //通过场景位置(ca.left|ca.top)设置scroll
1069     setScroll(type, v, offset){ //type = "left" | "top"; v = ca | number
1070         if(UTILS.isNumber(offset)  === false) offset = 0;
1071 
1072         if(this.isCA(v) === true) v = v[type];
1073 
1074         else if(UTILS.isNumber(v) === false) return this;
1075 
1076         if(type === "top"){
1077             if(this.#maxY <= this.box.h) this.setScrollTop(0);
1078             else this.setScrollTop((v + offset) / this.#maxY * (this.box.h - this.cursorBoxX.h));
1079             
1080         }
1081 
1082         else if(type === "left"){
1083             if(this.#maxX <= this.box.w) this.setScrollLeft(0);
1084             else this.setScrollLeft((v + offset) / this.#maxX * (this.box.w - this.cursorBoxY.w));
1085             
1086         }
1087 
1088         return this;
1089     }
1090 
1091     setScrollWidth(v){
1092         this.#scrollWidth = 
1093         this.cursorBoxX.h = 
1094         this.cursorBoxY.w = v;
1095 
1096         this.cursorBoxX.y = this.box.h - this.cursorBoxX.h;
1097         this.cursorBoxY.x = this.box.w - this.cursorBoxY.w;
1098 
1099         this.scrollX.size(this.box.w - this.cursorBoxY.w, this.cursorBoxX.h).rect().fill(this.#scrollColor).box.pos(0, this.cursorBoxX.y);
1100         this.scrollY.size(this.cursorBoxY.w, this.box.h - this.cursorBoxX.h).rect().fill(this.#scrollColor).box.pos(this.cursorBoxY.x, 0);
1101 
1102         v = this.#maxX;
1103         this.#maxX = 0;
1104         this._updateMaxX(v);
1105 
1106         v = this.#maxY;
1107         this.#maxY = 0;
1108         this._updateMaxY(v);
1109 
1110         v = this.cursorBoxX.x;
1111         this.cursorBoxX.x = NaN;
1112         this.setScrollLeft(v);
1113 
1114         v = this.cursorBoxY.y;
1115         this.cursorBoxY.y = NaN;
1116         this.setScrollTop(v);
1117 
1118         return this;
1119     }
1120 
1121 
1122     //以下方法限内部使用
1123 
1124     //通过视口位置设置scroll
1125     setScrollLeft(x){
1126         if(x < 0) x = 0;
1127         else{
1128             let _x = this.box.w - this.cursorBoxX.w - this.cursorBoxY.w;
1129             if(x > _x) x = _x;
1130         }
1131 
1132         if(this.cursorBoxX.x !== x){
1133             this.cursorBoxX.x = x;
1134             x = this.getScrollLeft();
1135             for(let k = 0, len = this.list.length; k < len; k++) this.list[k]._upateScrollX(x);
1136             return true;
1137         }
1138 
1139     }
1140 
1141     setScrollTop(y){
1142         if(y < 0) y = 0;
1143         else{
1144             let _y = this.box.h - this.cursorBoxY.h - this.cursorBoxX.h;
1145             if(y > _y) y = _y;
1146         }
1147         
1148         if(this.cursorBoxY.y !== y){
1149             this.cursorBoxY.y = y;
1150             y = this.getScrollTop();
1151             for(let k = 0, len = this.list.length; k < len; k++) this.list[k]._upateScrollY(y);
1152             return true;
1153         }
1154 
1155     }
1156 
1157     _bind(ca){
1158         var _x = ca.box.x, _y = ca.box.y, _w = ca.box.w, _h = ca.box.h, 
1159         _left = ca.box.x, _top = ca.box.y;
1160 
1161         Object.defineProperties(ca.box, {
1162             x: {
1163                 get: () => {return _x;},
1164                 set: v => {
1165                     _left = v;
1166                     _x = v - this.getScrollLeft();
1167                     this._updateMaxX(v + _w);
1168                 }
1169             },
1170             
1171             y: {
1172                 get: () => {return _y;},
1173                 set: v => {
1174                     _top = v;
1175                     _y = v - this.getScrollTop();
1176                     this._updateMaxY(v + _h);
1177                 }
1178             },
1179 
1180             w: {
1181                 get: () => {return _w;},
1182                 set: v => {
1183                     _w = v;
1184                     this._updateMaxX(_left + v);
1185                 }
1186             },
1187             
1188             h: {
1189                 get: () => {return _h;},
1190                 set: v => {
1191                     _h = v;
1192                     this._updateMaxY(_top + v);
1193                 }
1194             },
1195         });
1196 
1197         Object.defineProperties(ca, {
1198             left: {
1199                 get: function (){return _left;}
1200             },
1201             
1202             top: {
1203                 get: function (){return _top;}
1204             },
1205 
1206             width: {
1207                 get: function (){return _w;}
1208             },
1209 
1210             height: {
1211                 get: function (){return _h;}
1212             },
1213 
1214             _upateScrollX: {
1215                 value: scrollLeft => _x = _left - scrollLeft
1216             },
1217 
1218             _upateScrollY: {
1219                 value: scrollTop => _y = _top - scrollTop
1220             },
1221 
1222         });
1223 
1224         this._updateMaxX(_left + _w);
1225         this._updateMaxY(_top + _h);
1226         ca = undefined;
1227 
1228     }
1229 
1230     _bindScroll(cursor){
1231         var _xw = 0, _xh = 0;
1232         Object.defineProperties(cursor.box, {
1233             w: {
1234                 get: () => {return _xw;},
1235                 set: v => {
1236                     if(_xw === v) return;
1237                     _xw = 
1238                     cursor.image.width = v;
1239                     cursor.rect().fill(this.#cursorColor);
1240                     
1241                 }
1242             },
1243             
1244             h: {
1245                 get: () => {return _xh;},
1246                 set: v => {
1247                     if(_xh === v) return;
1248                     _xh = 
1249                     cursor.image.height = v;
1250                     cursor.rect().fill(this.#cursorColor);
1251 
1252                 }
1253             },
1254         });
1255 
1256         return cursor;
1257     }
1258 
1259     _unBind(ca){
1260         var v = ca.left;
1261         delete ca.box.x;
1262         ca.box.x = v;
1263 
1264         v = ca.top;
1265         delete ca.box.y;
1266         ca.box.y = v;
1267 
1268         v = ca.box.w;
1269         delete ca.box.w;
1270         ca.box.w = v;
1271 
1272         v = ca.box.h;
1273         delete ca.box.h;
1274         ca.box.h = v;
1275 
1276         delete ca._upateScrollX;
1277         delete ca._upateScrollY;
1278 
1279         //如果maxBox小于canvasBox则只重置maxBox
1280         if(this.#maxX <= this.box.w && this.#maxY <= this.box.h) return this._resetScroll();
1281         
1282         //当前的ca的box如果小于maxBox则不更新maxBox
1283         if(ca.width + ca.left < this.#maxX && ca.height + ca.top < this.#maxY) return;
1284         
1285         //更新maxBox
1286         this._resetScroll();
1287         for(let k = 0, len = this.list.length, _ca; k < len; k++){
1288             _ca = this.list[k];
1289             if(ca === _ca) continue;
1290 
1291             v = _ca.width + _ca.left;
1292             if(this.#maxX < v) this.#maxX = v;
1293 
1294             v = _ca.height + _ca.top;
1295             if(this.#maxY < v) this.#maxY = v;
1296 
1297         }
1298 
1299         //更新scrollBox
1300         v = this.box.w - this.cursorBoxY.w;
1301         if(this.box.w < this.#maxX) this.cursorBoxX.w = v / this.#maxX * v;
1302         else this.cursorBoxX.w = v;
1303 
1304         v = this.box.h - this.cursorBoxX.h;
1305         if(this.box.h < this.#maxY) this.cursorBoxY.h = v / this.#maxY * v;
1306         else this.cursorBoxY.h = v;
1307 
1308         this.setScrollLeft(this.cursorBoxX.x);
1309         this.setScrollTop(this.cursorBoxY.y);
1310 
1311     }
1312 
1313     _updateMaxX(mx){
1314         if(this.#maxX < mx){
1315             let w = this.box.w - this.cursorBoxY.w;
1316             if(this.box.w < mx) this.cursorBoxX.w = w / mx * w;
1317             else this.cursorBoxX.w = w;
1318             this.#maxX = mx;
1319             
1320         }
1321 
1322     }
1323 
1324     _updateMaxY(my){
1325         if(this.#maxY < my){
1326             let h = this.box.h - this.cursorBoxX.h;
1327             if(this.box.h < my) this.cursorBoxY.h = h / my * h;
1328             else this.cursorBoxY.h = h;
1329             this.#maxY = my;
1330             
1331         }
1332     }
1333 
1334     _resetScroll(){
1335         this.#maxX = 0;
1336         this.#maxY = 0;
1337         this.cursorBoxX.w = this.box.w - this.cursorBoxY.w;
1338         this.cursorBoxY.h = this.box.h - this.cursorBoxX.h;
1339 
1340     }
1341 
1342     _drawScroll(){
1343         if(super.isDraw(this.scrollX) === true) super._draw(this.scrollX);
1344         if(super.isDraw(this.scrollY) === true) super._draw(this.scrollY);
1345         if(super.isDraw(this.cursorX) === true) super._draw(this.cursorX);
1346         if(super.isDraw(this.cursorY) === true) super._draw(this.cursorY);
1347 
1348     }
1349 
1350 }
1351 
1352 
1353 
1354 
1355 /* CanvasAnimateOrdered 
1356 
1357 */
1358 class CanvasAnimateOrdered extends CanvasAnimateRenderScroll{
1359 
1360     constructor(option = {}){
1361         super(option);
1362 
1363     }
1364 
1365 }
1366 
1367 
1368 
1369 
1370 
1371 /* CanvasAnimate 
1372 parameter: 
1373     image
1374 
1375 attribute:
1376     opacity: Float; //透明度; 值0至1之间; 默认1;
1377     visible: Boolean; //默认true; 完全隐藏(既不绘制视图, 也不触发绑定的事件)
1378 
1379     //以下属性不建议直接修改
1380     box: Box; //.x.y是CanvasAnimateRender画布中的位置; .w.h是 this.image 的宽高
1381     circle: Circle; //边界圆, 默认 null;
1382     image: CanvasImageData; //默认 null; this.setImage(img) 修改
1383     overlap: Object; //CanvasAnimateRender.computeOverlaps(ca) 方法更新
1384     index: Integer; //CanvasAnimateRender.index(ca) 修改
1385 
1386     //只读
1387     width, height, top, left: Number; 
1388 
1389 method:
1390     scale(sx, sy: Number, isCenter: Bool): this;    //缩放
1391     sx, sy:        小于1缩放, 大于1放大, 等于1什么都不会做; 默认 1;
1392     isCenter:    缩放中心点是否居中; 默认 true;
1393 
1394     pos(x, y: Number): this;                        //设置this.box的x,y (既root的画布中位置)
1395     load(src: String, func: Function): Image;         //通过src加载图片完成后设置this.box .w.h
1396     setImage(img: ImageData): this;                 //设置this.image; this.box .w.h;
1397     computeCircle(): undefined;                     // 相对于.box计算.circle; 如果为null则创建一个新的 Circle
1398 
1399 demo:
1400     const img = new CanvasAnimateCustom().start(150, 150).rect(5)
1401         .fill("#000000") //背景
1402         .text("value", "red", 18, "center", "center") //文字
1403         .stroke("#ffffff") //边框
1404         .image;
1405         
1406     const ca = root.add(new CanvasAnimate().pos(10, 10).setImage(img));
1407     //root.redraw();
1408     //root.drawTarget(ca); //如果root已经render过了使用此方法绘制单独的ca, 比root.redraw()快2-3倍
1409     root.render();
1410 
1411 */
1412 class CanvasAnimate{
1413 
1414     constructor(image){
1415         this.opacity = 1;
1416         this.visible = true;
1417     
1418         //以下属性不建议直接修改
1419         this.box = new Box();
1420         this.circle = null;
1421         this.overlap = null;
1422         this.index = -1;
1423         
1424         //if(CanvasAnimateRender.isCA(image)) image = image.image;
1425         if(CanvasAnimateRender.isCanvasImage(image)){
1426             this.image = image;
1427             this.box.size(image.width, image.height);
1428         }
1429 
1430         else this.image = null;
1431 
1432     }
1433 
1434     pos(x, y){
1435         this.box.x = x;
1436         this.box.y = y;
1437         return this;
1438     }
1439 
1440     load(src, func){
1441         var img = new Image();
1442         
1443         img.onload = () => {
1444             this.box.size(img.width, img.height);
1445             if(typeof func === "function") func(img);
1446             func = img = null;
1447         }
1448 
1449         img.src = src;
1450         this.image = img;
1451         return img;
1452     }
1453 
1454     setImage(img = null){
1455         if(img !== null) this.box.size(img.width, img.height);
1456         this.image = img;
1457         return this;
1458     }
1459 
1460     computeCircle(inner){
1461         if(this.circle === null) this.circle = new Circle();
1462         this.circle.setFromBox(this.box, inner);
1463         return this;
1464     }
1465 
1466     get left(){return this.box.x;}
1467     get top(){return this.box.y;}
1468     get width(){return this.box.w;}
1469     get height(){return this.box.h;}
1470 
1471 }
1472 
1473 Object.defineProperties(CanvasAnimate.prototype, {
1474 
1475     isCanvasAnimate: {
1476         configurable: false,
1477         enumerable: false,
1478         writable: false,
1479         value: true,
1480     }
1481 
1482 });
1483 
1484 
1485 
1486 
1487 /* CanvasAnimateBox
1488     clear(box: Box): this; //box 如果未定义则清理整个画布
1489     shear(): HTMLCanvasElement; //this.image 拷贝到一个新的画布并返回
1490     size(w, h: Number): this; //设置画布宽高, 并初始化画布上下文
1491     shadow(shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY)
1492     rotateCenter(x, y) //设置旋转中心点
1493     transparentBG(size = 5, r, lineWidth) //默认透明背景
1494 
1495     rotate: Number; //旋转画布;
1496 
1497 
1498 demo:
1499     const image = new CanvasAnimateBox().size(100, 200).rect(10).fill('blue').image,
1500     box = new Box(0, 0, image.width, image.height),
1501     size = new Circle().setFromBox(box, false).r2; //获取旋转占用最大宽高
1502 
1503     const imgRed = new CanvasAnimateBox().size(size, size);
1504     imgRed.drawImage(image, box.center(imgRed.box));
1505     
1506     //render
1507     car.add(imgRed).pos(10, 10);
1508     car.render();
1509 
1510     //rotate
1511     new Timer(()=>{
1512         imgRed.rotate += 0.1;
1513         imgRed.drawImage(image, box);
1514         car.redraw();
1515     }, 1000, 60);
1516 
1517 */
1518 class CanvasAnimateBox extends CanvasAnimate{
1519 
1520     constructor(canvas){
1521         super(canvas);
1522         this._rotate = null;
1523 
1524         if(canvas !== this.image && CanvasAnimateRender.isCanvasImage(canvas)) this.context.drawImage(canvas, 0, 0);
1525 
1526     }
1527 
1528     clear(box){
1529         if(box === undefined) this.context.clearRect(0, 0, this.width, this.height);
1530         else this.context.clearRect(box.x, box.y, box.w, box.h);
1531         return this;
1532     }
1533 
1534     isEmpty(){
1535         return this.box.w < 1 || this.box.h < 1;
1536     }
1537 
1538     shear(box){
1539         const canvas = document.createElement("canvas");
1540 
1541         if(box === undefined){
1542             canvas.width = this.width;
1543             canvas.height = this.height;
1544             canvas.getContext("2d").drawImage(this.image, 0, 0);
1545         }
1546 
1547         else{
1548             canvas.width = box.w;
1549             canvas.height = box.h;
1550             canvas.getContext("2d").drawImage(this.image, box.x, box.y);
1551         }
1552         
1553         return canvas;
1554     }
1555 
1556     size(w, h, defaultStyles = false){
1557         w = (UTILS.isNumber(w) === true && w >= 1) ? w : this.width;
1558         h = (UTILS.isNumber(h) === true && h >= 1) ? h : this.height;
1559         this.image.width = this.box.w = w;
1560         this.image.height = this.box.h = h;
1561         if(defaultStyles === true) CanvasAnimateRender.setDefaultStyles(this.context);
1562         
1563         return this;
1564     }
1565 
1566     shadow(shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY){
1567         shadowColor = shadowColor || "rgba(0,0,0,0)";
1568         const con = this.context;
1569         if(con.shadowColor !== shadowColor) con.shadowColor = shadowColor;
1570         if(con.shadowBlur !== shadowBlur && typeof shadowBlur === "number") con.shadowBlur = shadowBlur;
1571         if(con.shadowOffsetX !== shadowOffsetX && typeof shadowOffsetX === "number") con.shadowOffsetX = shadowOffsetX;
1572         if(con.shadowOffsetY !== shadowOffsetY && typeof shadowOffsetY === "number") con.shadowOffsetY = shadowOffsetY;
1573         return this;
1574     }
1575 
1576     textWidth(text, font){
1577         if(this.#fontSize !== font){
1578             this.#fontSize = font;
1579             this.context.font = font+"px SimSun, Songti SC";
1580         }
1581         
1582         return this.context.measureText(text).width;
1583     }
1584 
1585     rotateCenter(x, y){
1586         if(UTILS.isNumber(x) === false) x = this.width / 2;
1587         if(UTILS.isNumber(y) === false) y = this.height / 2;
1588 
1589         if(this._rotate !== null){
1590             if(this._rotate.x !== x) this._rotate.x = x;
1591             if(this._rotate.y !== y) this._rotate.y = y;
1592             if(this._rotate.a !== 0){
1593                 this.context.translate(this._rotate.x, this._rotate.y);
1594                 this.context.rotate(-this._rotate.a);
1595                 this.context.translate(-this._rotate.x, -this._rotate.y);
1596                 //this.context.resetTransform();
1597                 this._rotate.a = 0;
1598             }
1599         }
1600 
1601         else this._rotate = {a: 0, x: x, y: y}
1602         
1603         return this;
1604     }
1605     
1606 
1607     //定义路径
1608     line(x, y, x1, y1){ //定义线段路径
1609         this.context.beginPath();
1610         this.context.moveTo(x, y);
1611         this.context.lineTo(x1, y1);
1612         
1613         return this;
1614     }
1615 
1616     path(arr, close = false, offsetX = 0, offsetY = 0){ //定义连续线条路径
1617         var len = arr.length;
1618 
1619         if(len >= 2){
1620             if(len % 2 !== 0) len -= 1;
1621             const con = this.context;
1622             con.beginPath();
1623             arr[0] += offsetX;
1624             arr[1] += offsetY;
1625             con.moveTo(arr[0], arr[1]);
1626             for(let k = 2; k < len; k+=2){
1627                 arr[k] += offsetX;
1628                 arr[k+1] += offsetY;
1629                 con.lineTo(arr[k], arr[k+1]);
1630             }
1631             if(close === true){
1632                 con.closePath();
1633                 if(arr[len -2] !== arr[0] || arr[len -1] !== arr[1]) arr.push(arr[0], arr[1]);
1634             }
1635         }
1636 
1637         return this;
1638     }
1639 
1640     _rect(r, box, lineWidth){ //定义边框路径
1641         const con = this.context;
1642         if(con.lineWidth !== lineWidth && lineWidth !== undefined) con.lineWidth = lineWidth;
1643         
1644         const s = con.lineWidth;
1645 
1646         if(UTILS.isObject(box) === false){
1647             var x = s / 2,
1648             y = s / 2,
1649             w = this.width - s,
1650             h = this.height - s;
1651         }
1652         
1653         else{
1654             var x = s / 2 + box.x,
1655             y = s / 2 + box.y,
1656             w = box.w - s,
1657             h = box.h - s;
1658         }
1659 
1660         if(UTILS.isNumber(r) === false || r <= 0){
1661             con.rect(x, y, w, h);
1662             return;
1663         }
1664 
1665         const _x = x + r, 
1666         _y = y + r, 
1667         mx = x + w, 
1668         my = y + h, 
1669         _mx = mx - r, 
1670         _my = my - r;
1671         
1672         //上
1673         con.moveTo(_x, y);
1674         con.lineTo(_mx, y);
1675         con.arcTo(mx, y, mx, _y, r);
1676 
1677         //右
1678         con.lineTo(mx, _y);
1679         con.lineTo(mx, _my);
1680         con.arcTo(mx, my, _x, my, r);
1681 
1682         //下
1683         con.lineTo(_x, my);
1684         con.lineTo(_mx, my);
1685         con.arcTo(x, my, x, _my, r);
1686 
1687         //左
1688         con.lineTo(x, _y);
1689         con.lineTo(x, _my);
1690         con.arcTo(x, y, _x, y, r);
1691 
1692     }
1693 
1694     rect(r, box, lineWidth){ //定义边框路径
1695         this.context.beginPath();
1696         this._rect(r, box, lineWidth);
1697 
1698         return this;
1699     }
1700 
1701     arc(circle){ //定义圆形路径;
1702         if(circle === undefined){
1703             this.computeCircle();
1704             circle = this.circle;
1705 
1706         }
1707         this.context.beginPath();
1708         this.context.arc(circle.x, circle.y, circle.r, 0, Math.PI * 2, false);
1709 
1710         return this;
1711     }
1712 
1713 
1714     //绘制路径
1715     stroke(color, lineWidth){ //绘制路径
1716         if(color && this.#strokeColor !== color){
1717             this.#strokeColor = color;
1718             this.context.strokeStyle = color;
1719         }
1720         if(this.context.lineWidth !== lineWidth && lineWidth !== undefined) this.context.lineWidth = lineWidth;
1721         this.context.stroke();
1722         
1723         return this;
1724     }
1725 
1726     fill(color){ //填充路径 evenodd
1727         if(color && this.#fillColor !== color){
1728             this.#fillColor = color;
1729             this.context.fillStyle = color;
1730         }
1731 
1732         this.context.fill();
1733 
1734         return this;
1735     }
1736 
1737     text(value, color, box){ //填充文字
1738         if(color && this.#fillColor !== color){
1739             this.#fillColor = color;
1740             this.context.fillStyle = color;
1741         }
1742 
1743         if(box === undefined) this.context.fillText(value, (this.width - this.textWidth(value, this.#fontSize))/2, (this.height - this.#fontSize) / 2);
1744         
1745         else{
1746             if(this.#fontSize !== box.h){
1747                 this.#fontSize = box.h;
1748                 this.context.font = box.h+"px SimSun, Songti SC";
1749             }
1750             
1751             this.context.fillText(value, box.x, box.y, box.w);
1752         }
1753         
1754         return this;
1755     }
1756 
1757     drawImage(img, x, y){
1758         if(y !== undefined) this.context.drawImage(img, x, y);
1759         else this.context.drawImage(img, x.x, x.y, x.w, x.h);
1760 
1761     }
1762 
1763     transparentBG(size = 5, r, box, lineWidth){
1764         const con = this.context,
1765         c1 = "rgb(255,255,255)", c2 = "rgb(127,127,127)", 
1766         lenX = Math.ceil(this.width/size), lenY = Math.ceil(this.height/size);
1767         
1768         con.save();
1769         con.beginPath();
1770         this._rect(r, box, lineWidth);
1771         con.clip();
1772 
1773         for(let ix = 0, iy = 0; ix < lenX; ix++){
1774 
1775             for(iy = 0; iy < lenY; iy++){
1776 
1777                 con.fillStyle = (ix + iy) % 2 === 0 ? c1 : c2;
1778                 con.fillRect(ix * size, iy * size, size, size);
1779                 
1780             }
1781 
1782         }
1783 
1784         con.restore();
1785         return this;
1786     }
1787 
1788 
1789     //
1790     get image(){
1791         return this.context.canvas;
1792     }
1793 
1794     set image(v){
1795 
1796         this.context = CanvasAnimateRender.getContext(v);
1797 
1798     }
1799 
1800     get rotate(){
1801         if(this._rotate === null) this._rotate = {a: 0, x: this.width / 2, y: this.height / 2}
1802         return this._rotate.a;
1803     }
1804 
1805     set rotate(a){
1806         if(this._rotate === null) this._rotate = {a: 0, x: this.width / 2, y: this.height / 2}
1807         if(this._rotate.a !== a){
1808             this.context.clearRect(-1, -1, this.width+2, this.height+2);
1809 
1810             this.context.translate(this._rotate.x, this._rotate.y);
1811             this.context.rotate(-this._rotate.a);
1812             this.context.rotate(a);
1813             this.context.translate(-this._rotate.x, -this._rotate.y);
1814             
1815             this._rotate.a = a;
1816 
1817         }
1818 
1819     }
1820 
1821     #fontSize = parseFloat(CanvasAnimateRender.defaultStyles.font);
1822     #fillColor = CanvasAnimateRender.defaultStyles.fillStyle;
1823     #strokeColor = CanvasAnimateRender.defaultStyles.strokeStyle;
1824 
1825 }
1826 
1827 
1828 
1829 
1830 /* CanvasAnimateCustom (this.image总是是一个canvas; 可以使用原生api绘制这个canvas)
1831 
1832 注意: 为了解决线模糊问题, 此类在绘制线时会做一些特殊处理: 线宽四舍五入, 线坐标点向上取整 (bug: 在用线拼凑形状时导致偏差)
1833 
1834 parameter: 
1835     canvas: Canvas; //可选; 默认新的 Canvas
1836 
1837 attribute: 
1838     image: Canvas;
1839     context: CanvasContext;             //你可以在这个 context 中自由绘制
1840     _path: Object{t: String, v: Array}     //当前定义的路径
1841     _rotate: Object{x,y,a}                //当前旋转
1842 
1843 method:
1844     size(w, h): this;                                  //设置画布宽高 (宽高变动时自动清除画布)
1845     clear(): this;                                      //清除画布
1846     unRotate(reset: Bool): this;                    //重置旋转; reset 重置旋转后是否将弧度设为零
1847     rotate(a, x, y, recover, updatePath): this;     //a为弧度, 默认0; 以画布中x,y为中心点旋转定义的路径, 默认.box的中心点;
1848     shadow(color, blur, offsetX, offsetY): this;    //阴影
1849     shear(x, y, w, h, canvas, dx, dy): canvas;         //把this.image的像素克隆到canvas上;
1850         x, y, dx, dy默认0; 
1851         w, h默认this.image 宽高; 
1852         canvas 默认新的canvas
1853     
1854     //定义路径
1855     line(x, y, x1, y1): this;                    //定义线段路径
1856     path(arr: Array[x, y], close: Bool): this;    //定义连续的线路径
1857     rect(r, x, y, w, h): this;                     //参数都是可选的
1858     arc(r, s, e, t): this;                         //定义圆形路径; (自动调用一次.computeCircle())
1859         //r 圆半径 默认this.circle.r, 
1860         //s 起始弧度 默认0, 
1861         //e 结束弧度 默认2PI, 
1862         //t false顺时针, true逆时针 默认false
1863 
1864     //绘制路径
1865     fill(color, lineWidth): this;                        //填充定义的路径
1866     stroke(color): this;                                 //绘制定义的路径
1867     drawImage(img, x, y, w, h, x1, y1, w1, h1): this;    //img 剪裁 到this.iamge上, 除img以外其它参数都是可选的(如果未定义宽高则自动设置为: img的宽高)
1868     img(img, posX, posY): this;                            //img 绘制 到this.iamge上, posX, posY根.text参数名类似
1869     text(value, color, size, posX, posY): this;            //填充文字(如果未定义宽高则自动设置为: 文字的宽高)
1870         value: String;
1871         color: Color;
1872         size: Number|String;
1873         posX: Number|"center|right";
1874         posY: Number|"center|bottom";
1875 
1876     //渐变
1877     linearGradient(x, y, x1, y1, colors, close): CanvasLinearGradient;         //参数都是可选的(起始到结束点)
1878     radialGradient(x, y, r, x1, y1, r1, colors): CanvasRadialGradient;    //参数都是可选的(起始到结束点和半径, 自动调用一次.computeCircle())
1879     gradientColor(gradient, colors, close): this;                         //给渐变对象添加渐变颜色;
1880     gradientColorSymme(gradient, colors): this;                            //长度为2: 0: colors[0] , 0.5: colors[1], 1: colors[0]; (如果长度为2除外的偶数则忽略最后一个颜色)
1881         gradient: CanvasLinearGradient | CanvasLinearGradient;
1882         colors: Array[CanvasColor];
1883 
1884 demo:
1885     const root = new CanvasAnimateRender({width: 750, height: 650, className: ""}), 
1886     cac = root.add(new CanvasAnimateCustom())
1887     .size(300, 300).pos(100, 100)                         //image的宽高 和 root画布中的位置
1888     .rect()                                                //定义矩形路径
1889     .fill("#fff")                                        //填充定义的路径
1890     .text("TEST", "#ff0000", 25, "center", "center")    //填充文字
1891     .stroke("#000");                                    //绘制定义的路径
1892 
1893     //渐变
1894     const colors = ["rgba(0,255,0,0)", "green"],
1895     lg = cac.linearGradient(),                        //创建线性渐变
1896     rg = cac.radialGradient();                         //创建径向渐变
1897     //cac.gradientColor(lg, colors).fill(lg);        //填充线性渐变
1898     cac.gradientColorSymme(rg, colors).fill(rg);    //填充径向渐变
1899 
1900     //阴影 旋转
1901     cac.shadow("#666", 4).path([100, 100, 150, 150, 100, 200]).stroke("red");
1902     //cac.rotate(10*Math.PI/180, 150, 150, true).stroke("#ff0000");
1903     cac.rotate(20*Math.PI/180).stroke("#00ff00");
1904     cac.rotate(40*Math.PI/180).stroke("#0000ff");
1905 
1906     root.render(); 
1907     console.log(root, cac)
1908 
1909 
1910     //自动设置宽高
1911     const img = new CanvasAnimateCustom()
1912     .text("TEST", "#fff", 25, "center", "center")
1913     .rect().stroke().image;
1914 
1915     root.add(new CanvasAnimateCustom()).drawImage(img);
1916     root.redraw();
1917 
1918 */
1919 class CanvasAnimateCustom extends CanvasAnimate{
1920 
1921     constructor(canvas){
1922         super();
1923         this._rotate = {a: 0, x: 0, y: 0}; //记录当前旋转弧度和中心点
1924         this._path = {t: "", v: [], d: true}; //记录外部定义的路径
1925         this.context = CanvasAnimateRender.getContext(canvas);
1926         this.image = this.context.canvas;
1927         
1928         if(this.image === canvas) this.size(this.image.width, this.image.height);
1929 
1930     }
1931 
1932     isEmpty(){
1933         return this.box.w < 1 || this.box.h < 1;
1934     }
1935 
1936     getData(x, y, w, h){
1937 
1938         return this.context.getImageData(x||0, y||0, w||this.box.w, h||this.box.h);
1939     }
1940 
1941     putData(data, x, y){
1942         this.context.putImageData(data, x||0, y||0);
1943         return this;
1944     }
1945 
1946     shear(x, y, w, h, canvas, dx, dy){
1947         if(typeof x !== "number") x = 0;
1948         if(typeof y !== "number") y = 0;
1949         if(typeof w !== "number") w = this.box.w;
1950         if(typeof h !== "number") h = this.box.h;
1951         if(typeof dx !== "number") dx = 0;
1952         if(typeof dy !== "number") dy = 0;
1953         
1954         const context = (canvas || document.createElement("canvas")).getContext("2d");
1955         if(context.canvas !== canvas){
1956             context.canvas.width = w;
1957             context.canvas.height = h;
1958         }
1959 
1960         if(this.isEmpty() === false) context.drawImage(this.image, x, y, w, h, dx, dy, w, h); //context.putImageData(this.context.getImageData(x, y, w, h), dx, dy);
1961 
1962         return context.canvas;
1963         
1964     }
1965 
1966     size(w, h){
1967         w = (typeof w === "number" && w >= 1) ? w : 1;
1968         h = (typeof h === "number" && h >= 1) ? h : 1;
1969         
1970         this.box.size(w, h);
1971         this.image.width = this.box.w;
1972         this.image.height = this.box.h;
1973         CanvasAnimateRender.setDefaultStyles(this.context);
1974         
1975         return this;
1976     }
1977 
1978     clear(x = 0, y = 0, w, h){
1979         this.context.clearRect(x, y, w || this.box.w, h || this.box.h);
1980         return this;
1981     }
1982 
1983     unRotate(setA){
1984         if(this._rotate.a !== 0){
1985             this.context.translate(this._rotate.x, this._rotate.y);
1986             this.context.rotate(-(this._rotate.a));
1987             this.context.translate(-(this._rotate.x), -(this._rotate.y));
1988             if(setA === true) this._rotate.a = 0;
1989         }
1990 
1991         return this;
1992     }
1993 
1994     rotate(a, x, y){
1995         a = typeof a === "number" ? a : 0;
1996         x = typeof x === "number" ? x : this.box.w/2;
1997         y = typeof y === "number" ? y : this.box.h/2;
1998 
1999         if(this._rotate.a !== a) this._rotate.a = a;
2000         if(this._rotate.x !== x) this._rotate.x = x;
2001         if(this._rotate.y !== y) this._rotate.y = y;
2002 
2003         if(this._rotate.a !== 0){
2004             this.context.translate(this._rotate.x, this._rotate.y);
2005             this.context.rotate(this._rotate.a);
2006             this.context.translate(-(this._rotate.x), -(this._rotate.y));
2007             //if(updatePath === true && this._path.t !== "") this[this._path.t]();
2008             
2009         }
2010         
2011         return this;
2012     }
2013 
2014     shadow(shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY){
2015         shadowColor = shadowColor || "rgba(0,0,0,0)";
2016         const con = this.context;
2017         if(con.shadowColor !== shadowColor) con.shadowColor = shadowColor;
2018         if(con.shadowBlur !== shadowBlur && typeof shadowBlur === "number") con.shadowBlur = shadowBlur;
2019         if(con.shadowOffsetX !== shadowOffsetX && typeof shadowOffsetX === "number") con.shadowOffsetX = shadowOffsetX;
2020         if(con.shadowOffsetY !== shadowOffsetY && typeof shadowOffsetY === "number") con.shadowOffsetY = shadowOffsetY;
2021         return this;
2022     }
2023 
2024     linearGradient(x, y, x1, y1, colors, close){
2025         const lg = this.context.createLinearGradient(x || 0, y || 0, x1 || this.box.w, y1 || 0);
2026         this.gradientColor(lg, colors, close);
2027         return lg;
2028     }
2029 
2030     radialGradient(x, y, r, x1, y1, r1, colors){
2031         this.computeCircle();
2032         const rg = this.context.createRadialGradient(x || this.circle.x, y || this.circle.y, r || this.circle.r/2, x1 || this.circle.x, y1 || this.circle.y, r1 || this.circle.r);
2033         this.gradientColor(rg, colors);
2034         return rg;
2035     }
2036 
2037 
2038     //定义路径
2039     line(x, y, x1, y1){ //定义线段路径
2040         this._path.t = "createLine";
2041         this._path.v.length = 0;
2042         this._path.v.push(x, y, x1, y1);
2043         this._path.d = false; //this.createLine();
2044         return this;
2045     }
2046 
2047     path(arr, close){ //定义连续线条路径
2048         this._path.t = "createPath";
2049         this._path.v.length = 0;
2050         this._path.v.push(arr, close);
2051         this._path.d = false; //this.createPath();
2052         return this;
2053     }
2054 
2055     rect(r, x, y, w, h){ //定义边框路径
2056         const s = this.context.lineWidth;
2057         r = r || s * 2;
2058         if(typeof x !== "number") x = 0;
2059         if(typeof y !== "number") y = 0;
2060         if(typeof w !== "number") w = this.box.w;
2061         if(typeof h !== "number") h = this.box.h;
2062         x += s / 2;
2063         y += s / 2;
2064         w -= s;
2065         h -= s;
2066         this._path.t = "createRect";
2067         this._path.v.length = 0;
2068         this._path.v.push(r, x, y, w, h);
2069         this._path.d = false; //this.createRect();
2070 
2071         return this;
2072     }
2073 
2074     arc(circle, s, e, t){ //定义圆形路径;
2075         circle = circle || this.circle;
2076         if(!circle) return this;
2077         //r = (typeof r === "number" && r) > 0 ? r : this.circle.r;
2078         s = typeof s === "number" ? s : 0;
2079         e = typeof e === "number" ? e : Math.PI * 2;
2080         t = typeof t === "boolean" ? t : false;
2081         this._path.t = "createArc";
2082         this._path.v.length = 0;
2083         this._path.v.push(circle, s, e, t);
2084         this._path.d = false; //this.createArc();
2085         
2086         return this;
2087     }
2088 
2089 
2090     //绘制路径
2091     stroke(color, lineWidth){ //绘制路径
2092         if(this._path.t === "" || this.isEmpty() === true) return;
2093         if(this.context.strokeStyle !== color && color) this.context.strokeStyle = color;
2094         if(this.context.lineWidth !== lineWidth && typeof lineWidth === "number") this.context.lineWidth = Math.round(lineWidth);
2095         if(this._path.d === false){
2096             this[this._path.t]();
2097             this._path.d = true;
2098         }
2099         this.context.stroke();
2100         
2101         return this;
2102     }
2103 
2104     fill(color){ //填充路径
2105         if(this._path.t === "" || this.isEmpty() === true) return;
2106         if(this.context.fillStyle !== color && color) this.context.fillStyle = color;
2107         if(this._path.d === false){
2108             this[this._path.t]();
2109             this._path.d = true;
2110         }
2111         this.context.fill();
2112         
2113         return this;
2114     }
2115 
2116     text(value, color, fontSize, posX, posY){ //填充文字
2117         const textBox = this.textWidth(value, fontSize), 
2118         height = Math.abs(textBox.fontBoundingBoxAscent) + Math.abs(textBox.fontBoundingBoxDescent);
2119 
2120         if(this.isEmpty() === true){
2121             const _font = this.context.font;
2122             this.size(textBox.width, height);
2123             if(this.isEmpty() === true) return this;
2124             this.context.font = _font;
2125         }
2126         
2127         if(typeof posX === "string"){
2128             if(textBox.width >= this.box.w) posX = 0;
2129             else{
2130                 switch(posX){
2131                     case "center":
2132                     posX = (this.box.w - textBox.width) / 2;
2133                     break;
2134                     
2135                     case "right":
2136                     posX = this.box.w - textBox.width;
2137                     break;
2138 
2139                     default: posX = 0; break;
2140                 }
2141             }
2142             
2143         }
2144 
2145         else posX = posX || 0;
2146         
2147         
2148         if(typeof posY === "string"){
2149             if(textBox.height >= this.box.h) posY = 0;
2150             else{
2151                 switch(posY){
2152                     case "center":
2153                     posY = (this.box.h - height) / 2;
2154                     break;
2155                     
2156                     case "bottom":
2157                     posY = this.box.h - height;
2158                     break;
2159 
2160                     default: posY = 0; break;
2161                 }
2162             }
2163             
2164         }
2165 
2166         else posY = posY || 0;
2167         
2168         if(this.context.fillStyle !== color && color) this.context.fillStyle = color;
2169         this.context.fillText(value, posX, posY);
2170         
2171         return this;
2172     }
2173 
2174     img(img, posX, posY, w, h){
2175         if(this.isEmpty() === true){
2176             this.size(img.width, img.height);
2177             if(this.isEmpty() === true){
2178                 console.warn("CanvasAnimateCustom: 内容为空");
2179                 return this;
2180             }
2181         }
2182 
2183         w = w || this.width;
2184         h = h || this.height;
2185 
2186         if(typeof posX === "string"){
2187             switch(posX){
2188                 case "center":
2189                 posX = (this.box.w - w) / 2;
2190                 break;
2191                 
2192                 case "right":
2193                 posX = this.box.w - w;
2194                 break;
2195             }
2196         }
2197 
2198         else posX = posX || 0;
2199         
2200         if(typeof posY === "string"){
2201             switch(posY){
2202                 case "center":
2203                 posY = (this.box.h - h) / 2;
2204                 break;
2205                 
2206                 case "bottom":
2207                 posY = this.box.h - h;
2208                 break;
2209             }
2210         }
2211 
2212         else posY = posY || 0;
2213         
2214         this.context.drawImage(img, posX, posY, w, h);
2215 
2216         return this;
2217     }
2218 
2219     drawImage(img, x, y, w, h, x1, y1, w1, h1){
2220         if(!img){
2221             console.warn("CanvasAnimateCustom: 参数错误");
2222             return this;
2223         }
2224 
2225         if(this.isEmpty() === true){
2226             this.size(img.width, img.height);
2227             if(this.isEmpty() === true){
2228                 console.warn("CanvasAnimateCustom: 内容为空");
2229                 return this;
2230             }
2231         }
2232 
2233         this.context.drawImage(img, x || 0, y || 0, w || img.width, h || img.height, x1 || 0, y1 || 0, w1 || this.box.w, h1 || this.box.h);
2234 
2235         return this;
2236     }
2237 
2238     drawTransparentBG(size, x, y, w, h){
2239         size = size || 5;
2240         x = x || 0;
2241         y = y || 0;
2242         w = w || this.box.w;
2243         h = h || this.box.h;
2244 
2245         const con = this.context, c1 = "rgb(255,255,255)", c2 = "rgb(127,127,127)", lenX = Math.ceil(w/size), lenY = Math.ceil(h/size);
2246         
2247         con.save();
2248         con.beginPath();
2249         con.rect(x, y, w, h);
2250         con.clip();
2251 
2252         for(let ix = 0, iy = 0; ix < lenX; ix++){
2253 
2254             for(iy = 0; iy < lenY; iy++){
2255 
2256                 con.fillStyle = (ix + iy) % 2 === 0 ? c1 : c2;
2257                 con.fillRect(ix * size + x, iy * size + y, size, size);
2258                 
2259             }
2260 
2261         }
2262 
2263         con.restore();
2264         return this;
2265     }
2266 
2267 
2268     //以下方法限内部使用
2269     _linePath(x, y, x1, y1, isStart){
2270         if(this.context.lineWidth % 2 === 1){
2271             if(x !== x1 && y !== y1){
2272                 x = Math.floor(x) + 0.5;
2273                 x1 = Math.floor(x1) + 0.5;
2274                 y = Math.floor(y) + 0.5;
2275                 y1 = Math.floor(y1) + 0.5;
2276             }
2277 
2278             else{
2279                 if(x === x1){
2280                     x = Math.floor(x) + 0.5;
2281                     x1 = Math.floor(x1) + 0.5;
2282                 }
2283                 else{
2284                     x = Math.floor(x);
2285                     x1 = Math.floor(x1);
2286                 }
2287 
2288                 if(y === y1){
2289                     y = Math.floor(y) + 0.5;
2290                     y1 = Math.floor(y1) + 0.5;
2291                 }
2292                 else{
2293                     y = Math.floor(y);
2294                     y1 = Math.floor(y1);
2295                 }
2296                 
2297             }
2298             
2299         }
2300 
2301         else{
2302             x = Math.floor(x);
2303             x1 = Math.floor(x1);
2304             y = Math.floor(y);
2305             y1 = Math.floor(y1);
2306         }
2307 
2308         if(isStart !== true) this.context.lineTo(x, y);
2309         else this.context.moveTo(x, y);
2310         this.context.lineTo(x1, y1);
2311 
2312     }
2313 
2314     createLine(){
2315         this.context.beginPath();
2316         this._linePath(this._path.v[0], this._path.v[1], this._path.v[2], this._path.v[3], true);
2317     }
2318 
2319     createPath(){
2320         const con = this.context, arr = this._path.v[0], len = arr.length;
2321         con.beginPath();
2322 
2323         this._linePath(arr[0], arr[1], arr[2], arr[3], true);
2324 
2325         for(let k = 4; k < len; k+=4){
2326             this._linePath(arr[k], arr[k+1], arr[k+2], arr[k+3]);
2327         }
2328 
2329         if(this._path.v[1] === true){
2330             //con.closePath();
2331             this._linePath(arr[len-2], arr[len-1], arr[0], arr[1]);
2332 
2333         }
2334 
2335     }
2336 
2337     createRect(){
2338         const con = this.context, r = Math.round(this._path.v[0]), x = this._path.v[1], y = this._path.v[2], w = this._path.v[3], h = this._path.v[4],
2339         _x = x + r, _y = y + r, mx = x + w, my = y + h, _mx = mx - r, _my = my - r;
2340         con.beginPath();
2341         
2342         //上
2343         con.moveTo(_x, y);
2344         con.lineTo(_mx, y);
2345         con.arcTo(mx, y, mx, _y, r);
2346 
2347         //右
2348         con.lineTo(mx, _y);
2349         con.lineTo(mx, _my);
2350         con.arcTo(mx, my, _x, my, r);
2351 
2352         //下
2353         con.lineTo(_x, my);
2354         con.lineTo(_mx, my);
2355         con.arcTo(x, my, x, _my, r);
2356 
2357         //左
2358         con.lineTo(x, _y);
2359         con.lineTo(x, _my);
2360         con.arcTo(x, y, _x, y, r);
2361 
2362     }
2363 
2364     createArc(){
2365         this.context.beginPath();
2366         this.context.arc(this._path.v[0].x, this._path.v[0].y, this._path.v[0].r, this._path.v[1], this._path.v[2], this._path.v[3]);
2367     }
2368     
2369     textWidth(text, font){
2370         if(typeof font === "number") this.context.font = font+"px SimSun, Songti SC";
2371         else if(font) this.context.font = font;
2372         
2373         const bounding = this.context.measureText(text);
2374 
2375         //兼容火狐 (暂时只用到这两个, 谷歌 和 火狐 获取的box属性名完全不一样)
2376         if(bounding.fontBoundingBoxAscent === undefined) bounding.fontBoundingBoxAscent = bounding.actualBoundingBoxAscent;
2377         if(bounding.fontBoundingBoxDescent === undefined) bounding.fontBoundingBoxDescent = bounding.actualBoundingBoxDescent;
2378 
2379         return bounding;
2380     }
2381 
2382     computeCircle(){
2383         super.computeCircle();
2384         this.circle.pos(this.width/2, this.height/2);
2385         return this;
2386     }
2387 
2388     static getPathTriangle(w, h, result){
2389         if(Array.isArray(result) === false) result = [w/2, 0, w, h, 0, h];
2390         else result.push(w/2, 0, w, h, 0, h);
2391         return result;
2392     }
2393 
2394     get gradientColor(){
2395         return CanvasAnimateRender.gradientColor;
2396     }
2397 
2398     get gradientColorSymme(){
2399         return CanvasAnimateRender.gradientColorSymme;
2400     }
2401 
2402 }
2403 
2404 
2405 
2406 
2407 /* CanvasAnimateImages (可以一次存储多个图像, 然后快速在其之间切换)
2408 parameter: 
2409     images: Array[CanvasImageData];
2410 
2411 attribute:
2412     images: Array[CanvasImageData]; //this.setImage(img) || this.images.push(img, ...);
2413 
2414 method:
2415     set(i: Integer): undefined; //设置当前image; i必须
2416     next(): undefined;
2417     loads(srcs: Array[String || Object{src}], onDone: Function(images, i, srcs), onUpdate: Function(images, i)): this; //通过给的src加载image; 加载完成后push到this.images; srcs 必须, 其它参数可选
2418 
2419 */
2420 class CanvasAnimateImages extends CanvasAnimate{
2421 
2422     #i = -1;
2423 
2424     constructor(images = []){
2425         super();
2426         this.images = images;
2427     
2428     }
2429 
2430     get i(){
2431         return this.#i;
2432     }
2433 
2434     _set(img){
2435         if(img !== null) this.box.size(img.width, img.height);
2436         this.image = img;
2437 
2438     }
2439 
2440     set(i){
2441         this.#i = i;
2442         this._set(this.images[i] || null);
2443         
2444     }
2445 
2446     next(){
2447         const len = this.images.length - 1;
2448         if(len !== -1){
2449             if(this.#i < len) this.#i++;
2450             else this.#i = 0;
2451             
2452             this._set(this.images[this.#i]);
2453 
2454         }
2455         
2456     }
2457 
2458     setImage(img = null){
2459         if(img === null) return;
2460         if(this.#i === -1){
2461             this.#i = this.images.length;
2462             this._set(img);
2463         }
2464 
2465         this.images.push(img);
2466         return this;
2467     }
2468 
2469     loads(srcs, onDone, onUpdate){
2470         onUpdate = typeof onUpdate === "function" ? onUpdate : null;
2471         var i = 0, c = srcs.length, img = null, _i = this.images.length;
2472 
2473         const len = srcs.length, 
2474         func = ()=>{
2475             i++; if(onUpdate !== null) onUpdate(this.images, _i);
2476             if(i === c && typeof onDone === "function"){
2477                 if(this.#i === -1) this.set(0);
2478                 onDone(this.images, _i, srcs);
2479             }
2480             else _i++;
2481         }
2482 
2483         for(let k = 0, ty = ""; k < len; k++){
2484             ty = typeof srcs[k];
2485             if(ty === "string" || ty === "object"){
2486                 ty = ty === "string" ? srcs[k] : srcs[k].src;
2487                 if(ty !== "" && typeof ty === "string"){
2488                     img = new Image();
2489                     img.onload = func;
2490                     this.images.push(img);
2491                     img.src = ty;
2492                 }
2493                 else c--;
2494             }
2495 
2496         }
2497 
2498         return this;
2499     }
2500 
2501 }
2502 
2503 
2504 
2505 
2506 /* CanvasAnimateSprite (把精灵图缓存为多个图像, 然后快速在其之间切换)
2507 parameter: 
2508 attribute:
2509 method:
2510     buffer(image: ImageData, data: Array[Box] || Object{w, h}): this; //把精灵图剪裁为canvas; 然后缓存到images队列;
2511     
2512 */
2513 class CanvasAnimateSprite extends CanvasAnimateImages{
2514 
2515     constructor(images){
2516         super(images);
2517 
2518     }
2519 
2520     buffer(image, data){
2521         if(!image || !data) return console.warn("CanvasAnimateSprite: 参数错误, 缓存失败");
2522         
2523         const paramCon = CanvasAnimateRender.paramCon;
2524         if(Array.isArray(data) === true){
2525             const len = data.length;
2526             for(let k = 0, context, canvas, box; k < len; k++){
2527                 box = data[k];
2528                 if(box.w < 1 || box.h < 1) continue;
2529 
2530                 canvas = document.createElement("canvas");
2531                 canvas.width = box.w;
2532                 canvas.height = box.h;
2533                 context = canvas.getContext("2d", paramCon);
2534                 
2535                 context.drawImage(image, box.x, box.y, box.w, box.h, 0, 0, box.w, box.h);
2536                 this.setImage(canvas);
2537                 
2538             }
2539         }
2540 
2541         else{
2542             if(data.w < 1 || data.h < 1) return console.warn("CanvasAnimateSprite: box小于1");
2543             const mw = image.width, mh = image.height,
2544             lenX = Math.floor(mw / data.w),
2545             lenY = Math.floor(mh / data.h);
2546 
2547             for(let x = 0, y, context, canvas, _x; x < lenX; x++){
2548                 _x = x * data.w;
2549                 for(y = 0; y < lenY; y++){
2550                     canvas = document.createElement("canvas");
2551                     canvas.width = data.w;
2552                     canvas.height = data.h;
2553                     context = canvas.getContext("2d", paramCon);
2554 
2555                     context.drawImage(image, _x, y * data.h, data.w, data.h, 0, 0, data.w, data.h);
2556                     this.setImage(canvas);
2557                 }
2558             
2559             }
2560 
2561         }
2562         
2563         return this;
2564     }
2565 
2566 }
2567 
2568 
2569 
2570 
2571 /* ColorTestViewer 颜色调试器
2572 
2573 attribute:
2574     onchange: Function; //颜色改变回调; 默认null
2575 
2576     //以下属性不建议直接修改
2577     rgb: RGBColor;         //rgb模式颜色
2578     hsv: Object{h,s,v};    //hsv模式颜色
2579     alpha: Number;        //透明度
2580 
2581 method:
2582     update(): undefined;        //更新控件视图 (通常控件被唤出时调用此方法, 前提是在唤出前控件颜色发生了改变)
2583     setValue(r, g, b, a): this; //设置控件的颜色
2584 
2585 demo:
2586     const ctv = new ColorTestViewer({width: 200})
2587     .setValue(0, 0, 255, 1).update(false)
2588     .pos(100, 100).render();
2589 
2590     ctv.onchange = v => console.log(v);
2591 
2592 */
2593 class ColorTestViewer extends CanvasAnimateRender{
2594 
2595     get value(){
2596         return this.rgb.getRGBA(Math.floor(this.alpha * 1000) / 1000);
2597     }
2598 
2599     constructor(option = {}){
2600         super(option);
2601 
2602         this.rgb = new RGBColor(255);
2603         this.hsv = this.rgb.getHSV();
2604         this.alpha = 1;
2605         this.cae = null;
2606         this.onchange = null;
2607 
2608         this.viewSVColor = null;
2609         this.viewSVsv = null;
2610         this.viewSVCursor = null;
2611 
2612         this.viewHValueBG = null;
2613         this.viewHValue = null;
2614         this.viewHScroll = null;
2615         this.viewHCursor = null;
2616 
2617         this.viewAScroll = null;
2618         this.viewACursor = null;
2619 
2620         this.viewColorInfo = null;
2621 
2622         //默认样式
2623         if(option.canvas === undefined){
2624             const width = option.width || 200, height = option.height || (width * 0.8), margin = 2, 
2625             h5 = height * 0.5, h1 = height * 0.1, h3 = height * 0.3;
2626 
2627             this.size(width + margin * 2, height + margin * 5);
2628 
2629             this.initViewSVColor(width, h5);
2630             this.initViewHScroll(width, h1);
2631             this.initViewAScroll(width, h1);
2632             this.initViewHValue(h3, h3);
2633             this.initViewColorInfo(width - h3, h3);
2634 
2635             this.setViewSVPos(margin, margin);
2636             this.setViewHScrollPos(margin, h5 + margin * 2);
2637             this.setViewAScrollPos(margin, h5 + h1 + margin * 3);
2638             this.setViewHValuePos(margin, h5 + h1 * 2 + margin * 4);
2639             this.viewColorInfo.pos(this.viewHValue.box.maxX(), this.viewHValue.box.y);
2640             
2641             this.initList();
2642             this.initEvent();
2643 
2644         }
2645         
2646     }
2647 
2648     update(u){
2649         if(this.viewSVColor !== null){
2650             this.updateViewSVCursor();
2651             this.updateViewSVColor();
2652             this.updateViewHValue();
2653             this.updateViewHCursor();
2654             this.updateViewACursor();
2655             this.updateViewColorInfo();
2656             if(u === true) this.redraw();
2657         }
2658 
2659         return this;
2660     }
2661 
2662     setValue(r, g, b, a){
2663         this.rgb.r = r;
2664         this.rgb.g = g;
2665         this.rgb.b = b;
2666         this.alpha = a;
2667         this.rgb.getHSV(this.hsv);
2668         
2669         return this;
2670     }
2671 
2672     setValueString(color){
2673         if(typeof color !== "string") return this;
2674         var _color = this.getColor(color);
2675         
2676         if(_color[0] === "#"){
2677             const len = _color.length;
2678             if(len === 4){
2679                 _color = _color.slice(1);
2680                 this.rgb.setFormHex(parseInt("0x"+_color + "" + _color));
2681             }
2682             else if(len === 7) this.rgb.setFormHex(parseInt("0x"+_color.slice(1)));
2683             this.alpha = 1;
2684             this.rgb.getHSV(this.hsv);
2685             
2686         }
2687 
2688         else if(_color[0] === "r" && _color[1] === "g" && _color[2] === "b"){
2689             const arr = [];
2690             for(let k = 0, len = _color.length, v = "", is = false; k < len; k++){
2691                 
2692                 if(is === true){
2693                     if(_color[k] === "," || _color[k] === ")"){
2694                         arr.push(parseFloat(v));
2695                         v = "";
2696                     }
2697                     else v += _color[k];
2698                     
2699                 }
2700 
2701                 else if(_color[k] === "(") is = true;
2702                 
2703             }
2704 
2705             this.setValue(arr[0], arr[1], arr[2], arr[3] === undefined ? 1 : arr[3]);
2706             
2707         }
2708         
2709         return this;
2710     }
2711 
2712     getColor(str){ //检测 str 是否是颜色类型(16进制, rgb, rgba, 英文); 如果是则返回去除掉空格后的字符串颜色(英文则返回对应16进制颜色)
2713         var _color = "";
2714         for(let k = 0, len = str.length; k < len; k++){
2715             if(str[k] === " ") continue;
2716             _color += str[k];
2717         }
2718         
2719         if(_color[0] === "#" || (_color[0] === "r" && _color[1] === "g" && _color[2] === "b")) return _color;
2720         else{
2721             for(let k = 0, len = ColorRefTable.length; k < len; k++){
2722                 str = ColorRefTable[k];
2723                 if(str[0] === _color) return str[1];
2724             }
2725         }
2726 
2727         return "";
2728     }
2729 
2730     exit(){
2731         if(this.cae){
2732             this.onUpSV();
2733             this.onUpH();
2734             this.onUpA();
2735         }
2736 
2737         if(this.domElement.parentElement) this.domElement.parentElement.removeChild(this.domElement);
2738 
2739     }
2740 
2741 
2742     //event
2743     initEvent(){
2744         
2745         const cae = new CanvasAnimateEvent(this);
2746 
2747         //SV
2748         const setSV = (pageX, pageY) => {
2749             pageX = (pageX - this.domElementRect.x - this.viewSVColor.box.x) / this.viewSVColor.box.w * 100;
2750             pageY = (1 - (pageY - this.domElementRect.y - this.viewSVColor.box.y) / this.viewSVColor.box.h) * 100;
2751             if(pageX < 0) pageX = 0;
2752             else if(pageX > 100) pageX = 100;
2753             if(pageY < 0) pageY = 0;
2754             else if(pageY > 100) pageY = 100;
2755             if(this.hsv.s !== pageX || this.hsv.v !== pageY){
2756                 this.hsv.s = pageX;
2757                 this.hsv.v = pageY;
2758                 this.rgb.setFormHSV(this.hsv.h, this.hsv.s, this.hsv.v);
2759                 if(typeof this.onchange === "function") this.onchange(this.value);
2760                 this.updateViewHValue();
2761                 this.updateViewColorInfo();
2762                 this.updateViewSVCursor();
2763                 this.redraw();
2764             }
2765 
2766         },
2767 
2768         onMoveSV = event => {
2769             setSV(event.pageX, event.pageY);
2770         },
2771 
2772         onUpSV = () => {
2773             document.body.removeEventListener("pointerup", onUpSV);
2774             document.body.removeEventListener("pointermove", onMoveSV);
2775             cae.remove(this.viewSVCursor, 'up', onUpSV);
2776             cae.remove(this.viewSVCursor, 'move', onMoveSV);
2777             
2778         },
2779 
2780         onDownSV = event => {
2781             setSV(event.pageX, event.pageY);
2782             cae.add(this.viewSVCursor, "up", onUpSV);
2783             cae.add(this.viewSVCursor, "move", onMoveSV);
2784             document.body.addEventListener("pointerup", onUpSV);
2785             document.body.addEventListener("pointermove", onMoveSV);
2786             
2787         }
2788 
2789         cae.add(this.viewSVColor, "down", onDownSV);
2790         cae.add(this.viewSVCursor, "down", onDownSV);
2791         this.onUpSV = onUpSV;
2792 
2793 
2794         //H
2795         const setH = (pageX) => {
2796             pageX = (pageX - this.domElementRect.x - this.viewHScroll.box.x) / this.viewHScroll.box.w * 360;
2797             if(pageX < 0) pageX = 0;
2798             else if(pageX > 360) pageX = 360;
2799             if(this.hsv.h !== pageX){
2800                 this.hsv.h = pageX;
2801                 this.rgb.setFormHSV(this.hsv.h, this.hsv.s, this.hsv.v);
2802                 if(typeof this.onchange === "function") this.onchange(this.value);
2803                 this.updateViewHValue();
2804                 this.updateViewColorInfo();
2805                 this.updateViewSVColor();
2806                 this.updateViewHCursor();
2807                 this.redraw();
2808             }
2809 
2810         },
2811 
2812         onMoveH = event => {
2813             setH(event.pageX);
2814         },
2815 
2816         onUpH = () => {
2817             document.body.removeEventListener("pointerup", onUpH);
2818             document.body.removeEventListener("pointermove", onMoveH);
2819             cae.remove(this.viewHCursor, 'up', onUpH);
2820             cae.remove(this.viewHCursor, 'move', onMoveH);
2821 
2822         },
2823 
2824         onDownH = event => {
2825             setH(event.pageX);
2826             cae.add(this.viewHCursor, "up", onUpH);
2827             cae.add(this.viewHCursor, "move", onMoveH);
2828             document.body.addEventListener("pointerup", onUpH);
2829             document.body.addEventListener("pointermove", onMoveH);
2830 
2831         }
2832         
2833         cae.add(this.viewHScroll, "down", onDownH);
2834         cae.add(this.viewHCursor, "down", onDownH);
2835         this.onUpH = onUpH;
2836 
2837 
2838 
2839         //A
2840         const setA = (pageX) => {
2841             pageX = (pageX - this.domElementRect.x - this.viewAScroll.box.x) / this.viewAScroll.box.w;
2842             if(pageX < 0) pageX = 0;
2843             else if(pageX > 1) pageX = 1;
2844             if(this.alpha !== pageX){
2845                 this.alpha = pageX;
2846                 if(typeof this.onchange === "function") this.onchange(this.value);
2847                 this.updateViewColorInfo();
2848                 this.updateViewHValue();
2849                 this.updateViewACursor();
2850                 this.redraw();
2851             }
2852 
2853         },
2854 
2855         onMoveA = event => {
2856             setA(event.pageX);
2857         },
2858 
2859         onUpA = () => {
2860             document.body.removeEventListener("pointerup", onUpA);
2861             document.body.removeEventListener("pointermove", onMoveA);
2862             cae.remove(this.viewACursor, 'up', onUpA);
2863             cae.remove(this.viewACursor, 'move', onMoveA);
2864             
2865         },
2866 
2867         onDownA = event => {
2868             setA(event.pageX);
2869             cae.add(this.viewACursor, "up", onUpA);
2870             cae.add(this.viewACursor, "move", onMoveA);
2871             document.body.addEventListener("pointerup", onUpA);
2872             document.body.addEventListener("pointermove", onMoveA);
2873 
2874         }
2875 
2876         cae.add(this.viewAScroll, "down", onDownA);
2877         cae.add(this.viewACursor, "down", onDownA);
2878         this.onUpA = onUpA;
2879 
2880         this.cae = cae;
2881 
2882     }
2883 
2884 
2885     //SV 明度 与 灰度
2886     updateViewSVCursor(){
2887         this.viewSVCursor.pos(this.hsv.s / 100 * this.viewSVColor.box.w + this.viewSVColor.box.x - this.viewSVCursor.circle.r, (1 - this.hsv.v / 100) * this.viewSVColor.box.h + this.viewSVColor.box.y - this.viewSVCursor.circle.r);
2888     }
2889 
2890     updateViewSVColor(){
2891         this.viewSVColor.clear().fill(ColorTestViewer.emptyColor.setFormHSV(this.hsv.h, 100, 100).getHexString());
2892 
2893     }
2894 
2895     setViewSVPos(x, y){
2896         this.viewSVColor.pos(x, y);
2897         this.viewSVsv.pos(x, y);
2898         this.updateViewSVCursor();
2899     }
2900 
2901     initViewSVColor(width, height){ //*3
2902         this.viewSVColor = new CanvasAnimateCustom().size(width, height).rect();
2903 
2904         this.viewSVsv = new CanvasAnimateCustom().size(width, height);
2905         const gradientS = this.viewSVsv.linearGradient(0, height, width, height, ColorTestViewer.colorS, true),
2906         gradientV = this.viewSVsv.linearGradient(width, height, width, 0, ColorTestViewer.colorV, true);
2907         this.viewSVsv.rect().fill(gradientS).fill(gradientV);
2908 
2909         this.viewSVCursor = new CanvasAnimateCustom().size(10, 10);
2910         this.viewSVCursor.computeCircle();
2911         this.viewSVCursor.arc().stroke("#fff");
2912 
2913         this.list.push(this.viewSVColor, this.viewSVsv, this.viewSVCursor);
2914 
2915         this.setViewSVPos(0, 0);
2916         this.updateViewSVColor();
2917 
2918     }
2919 
2920 
2921     //H 颜色
2922     updateViewHValue(){
2923         this.viewHValue.clear().fill(this.rgb.getRGBA(this.alpha));
2924 
2925     }
2926 
2927     setViewHValuePos(x, y){
2928         this.viewHValueBG.pos(x, y);
2929         this.viewHValue.pos(x, y);
2930 
2931     }
2932 
2933     initViewHValue(width, height){ //*2
2934         this.viewHValueBG = new CanvasAnimateCustom().size(width, height)
2935         .drawTransparentBG(5, 0, 0, width, height);
2936 
2937         this.viewHValue = new CanvasAnimateCustom().size(width, height)
2938         .rect();
2939 
2940         this.list.push(this.viewHValueBG, this.viewHValue);
2941         this.updateViewHValue();
2942 
2943     }
2944 
2945     updateViewHCursor(){
2946         this.viewHCursor.pos(this.hsv.h / 360 * this.viewHScroll.box.w + this.viewHScroll.box.x - this.viewHCursor.circle.r, this.viewHScroll.box.y);
2947 
2948     }
2949 
2950     setViewHScrollPos(x, y){
2951         this.viewHScroll.pos(x, y);
2952         this.updateViewHCursor();
2953     }
2954 
2955     initViewHScroll(width, height){ //*2
2956         this.viewHScroll = new CanvasAnimateCustom().size(width, height).rect();
2957         this.viewHScroll.fill(this.viewHScroll.linearGradient(0, height, width, height, ColorTestViewer.colorH, true));
2958 
2959         const size = Math.min(width, height);
2960         this.viewHCursor = new CanvasAnimateCustom().size(size, size);
2961         this.viewHCursor.computeCircle();
2962         this.viewHCursor.arc().stroke("#fff");
2963         
2964         this.list.push(this.viewHScroll, this.viewHCursor);
2965         this.setViewHScrollPos(0, 0);
2966 
2967     }
2968 
2969 
2970     //A 透明度
2971     updateViewACursor(){
2972         this.viewACursor.pos(this.alpha * this.viewAScroll.box.w + this.viewAScroll.box.x - this.viewACursor.circle.r, this.viewAScroll.box.y);
2973 
2974     }
2975 
2976     setViewAScrollPos(x, y){
2977         this.viewAScroll.pos(x, y);
2978         this.updateViewACursor();
2979     }
2980 
2981     initViewAScroll(width, height){ //*2
2982         this.viewAScroll = new CanvasAnimateCustom().size(width, height)
2983         .drawTransparentBG(5, 0, 0, width, height).rect();
2984         this.viewAScroll.fill(this.viewAScroll.linearGradient(0, height, width, height, ColorTestViewer.colorA));
2985 
2986         const size = Math.min(width, height);
2987         this.viewACursor = new CanvasAnimateCustom().size(size, size);
2988         this.viewACursor.computeCircle();
2989         this.viewACursor.arc().stroke("rgb(0,160,255)");
2990 
2991         this.list.push(this.viewAScroll, this.viewACursor);
2992         this.setViewAScrollPos(0, 0);
2993 
2994     }
2995 
2996 
2997     //color text
2998     updateViewColorInfo(){
2999         
3000         this.viewColorInfo.clear().text(this.value, "#000000", 12, "center", "center");
3001 
3002     }
3003 
3004     initViewColorInfo(width, height){ //*1
3005         this.viewColorInfo = new CanvasAnimateCustom().size(width, height);
3006         this.list.push(this.viewColorInfo);
3007         this.updateViewColorInfo();
3008     }
3009 
3010 
3011 
3012     static emptyColor = new RGBColor();
3013 
3014     static colorH = function (){ //颜色渐变
3015         const result = [], color = ColorTestViewer.emptyColor;
3016         for(let h = 0; h < 6; h++){
3017             color.setFormHSV(h/6*360, 100, 100);
3018             result.push(color.getHexString());
3019         }
3020         
3021         return result;
3022     }();
3023 
3024     static colorS = function (){ //饱和度的渐变
3025         const result = [];
3026         for(let s = 0; s < 100; s++) result.push('rgba(255,255,255,' + (1 - s / 100) + ")");
3027         return result;
3028     }();
3029 
3030     static colorV = function (){ //明度渐变
3031         const result = [];
3032         for(let s = 0; s < 100; s++) result.push('rgba(0,0,0,' + (1 - s / 100) + ")");
3033         return result;
3034     }();
3035 
3036     static colorA = function (){ //透明度渐变
3037         const result = [];
3038         for(let a = 0; a <= 10; a++) result.push('rgba(0,0,0,' + (a / 10) + ")");
3039         return result;
3040     }();
3041 
3042 }
3043 
3044 
3045 
3046 
3047 /* MenuView Tree Option Menu 树状选项菜单视图
3048 注意: MenuView 在初始化时用时比较长;  MenuView一旦被实例化后 .visible, .views 除外的属性更新将无效, 因为它们已经被缓存为图像
3049 
3050 parameter:
3051     names: Array[String], funcs, lcons, parentElem(所有后代引用父的.parentElem)
3052     或第一个参数 Array[Object]: [
3053         {
3054             name: String, //对应 .names 必须
3055             lcon: ImageData, //对应 .lcons; (MenuView.lconSize 是每个lcon的宽高) 可选
3056             func: Function //对应 .funcs; 可选
3057         }
3058         ...
3059     ]
3060 
3061 attribute:
3062     parentElem: DomElement; //父容器 默认 null 既body (注意: 你如果要自定义此属性值, 只需要给root(最顶层且没有父的MenuView)传一次就够了,因为root的后代会自动引用自己上级的.parentElem)
3063     visible: Bool; 是否显示视图 默认 false (如果当前MenuView是root(没有父), 则需要手动更新, 事实上root的视图位置(.view.pos) 和 是否显示(.visible) 都需要自己手动更新)
3064 
3065     //以下属性只读; 内部自动定义
3066     top, left, widht, height: Number; //canvas的位置和宽高, 只读
3067     view: CanvasAnimateRender; //用于渲染 CanvasAnimate
3068     events: CanvasAnimateEvent; //CanvasAnimateRender 的事件管理器
3069     views: Array[funcs.callback.v]; //
3070     _eventTargets: Array[CanvasAnimateImages]; //.exit()方法用此属性清理事件
3071     parentElemRect: Box; //.parentElem的位置和宽高(root负责创建和更新此值, 其后代只是引用)
3072     names: Array[String]; //文本内容 默认[];
3073     lcons: Array[][CanvasImageData]; //自定义图标; 对应 names 索引 默认null;
3074     funcs: Array[Function]; //onclick事件回调; 对应 names 索引 默认null;
3075         callback: 
3076             v: Object{
3077                 //以下属性是可选的 默认null
3078                 background: CanvasAnimateImages, //背景, 最底部的cai 
3079                 border: CanvasAnimateImages, //边框 顶部的cai
3080                 lcon_def: CanvasAnimateImages, //系统默认的图标, 0: "✘", 1: "✔", 2: "●", 3: "◆", 4: "■", 5: "★"; (更换对钩✔图标: lcon_def.set(1), 参见: static/defaultLcon)
3081                 lcon: CanvasAnimateImages, //自定义的图标 对应自定义的.lcons (注意: lcon_def 和 lcon 的位置是重叠的, lcon在lcon_def的上层)
3082 
3083                 //以下属性总会被定义 必须
3084                 name: CanvasAnimateImages, //如要获取对应的name值: target.names[key], 回调函数: .funcs[key] 图标: .lcons[key]
3085                 child: CanvasAnimateImages, //右箭头图标 (如果 children[key] 未定义 则.visible = false, 所以可以动态添加,删除,设置,某项的后代)
3086                 disable: Bool, //是否禁用; 默认 false
3087                 key: Number, //对应 .names 的键值
3088                 target: MenuView, //this
3089             },
3090             event: onpointerup Event
3091     
3092 method:
3093     getViewByName(name): Object{funcs.callback.v}; //返回的对象 恒等于 回调返回的参数对象
3094     add(v: MenuView, k: Number): v; //把v添加到自己的子集; k对应.names的键, 如果未定义直接.push(v)
3095     exit(): undefined; //清理自己及自己所有后代的缓存和视图
3096     
3097     //内部自动调用以下方法:
3098     update(): undefined; //更新自己及自己第一层后代位置, .visible 设为true时自动调用一次; 如果更新自己及所有后代: this.traverse(this._update); (如果当前MenuView是root(没有父), 则需要手动更新位置: MenuView.view.pos(left:Number, top:Number));
3099     traverse(callback): undefined; //遍历自己及后代
3100         callback(v: MenuView, k: v在其父的键, p: v的父亲) //如果v存在则返回两个参数, 否则返回三个参数
3101     initView(names);
3102     clearNowView(k)
3103     importData();
3104     _setHiddenOrChild(k);
3105     _update(v, k, p);
3106     _traverse(callback, k);
3107 
3108 static: 
3109     sign: String; //如果定义给后续创建的每个canvas对象添加一条属性(canvas[sign] = true); 默认 "" 既不添加
3110     parentElem: DomElement; //.parentElem 的备胎 默认 null;
3111     className: String; //css类名; 默认 ""
3112     lconSize: Number; //lcon 的宽高 默认 16
3113     textSize: Number; //全局的文字大小 默认12 (注意此值不能超过 lconSize)
3114     borderSize: Number; //如果小于等于0初始化时不创建border; 默认0
3115     textColor: Object; //默认 {down: "#0000ff", up: "#000000", disable: "#666666"};
3116     rowBorderColor: Object; //默认 {down: "#ffffff", up: "#000000", disable: "#666666"};
3117     rowBackgroundColor: Object; //如果为null则不创建背景; 默认 {down: "rgba(0,0,255,0.2)", up: "#ffffff", disable: "#333333"}
3118     rowPadding: Object; //每排的内边距 默认 {top: 2, right: 2, bottom: 2, left: 2}
3119     rowMargin: Object; //每排的外边距 默认 {top: 2, right: 2, bottom: 2, left: 2};
3120     defaultLcon: String; //是否为每排缓存默认的图标(lcon_def); 默认 "min" | "" | "max"; ["✘", "✔", "●", "◆", "■", "★"]; min 缓存0,1,2; ""不缓存; max缓存全部
3121     buttonType: Number; // -1鼠标左右键都可触发回调 | 0只能是左键 | 2只能是右键
3122 
3123     reset(): undefined; //重置上面所有静态属性的值(恢复其默认值, 所有静态属性只对后续创建的 MenuView 有效)
3124     
3125     _cac: CanvasAnimateCustom;
3126     nowView: MenuView;
3127     clearNowView(): undefined;
3128 
3129 private:
3130     length: Number;
3131     visible: Bool;
3132 
3133 demo:
3134     const func = v => {
3135         console.log(v);
3136         v.lcon_def.set(1); //"✔"图标
3137         //v.disable = true; //禁用
3138     },
3139     names = ["AAA", "BBB", "CCC", "DDDDDDDDD"],
3140     funcs = [func, null, func],
3141     lcons = null,
3142 
3143     root = new MenuView(names, null, funcs, lcons);
3144 
3145     //对应 root.names[2]: "CCC"
3146     root.add(new MenuView(names, null, funcs, lcons), 2);
3147 
3148     //对应 root.names[3]: "DDDDDDDDD"
3149     root.add(new MenuView(names, null, funcs, lcons), 3);
3150 
3151     root.view.pos(10, 10); //设置root的位置
3152     root.visible = true; //显示视图
3153 
3154     //
3155     const root = new MenuView([
3156         {name: "AAA", func: func},
3157         {name: "bbb", func: func},
3158         {name: "ccc", func: func},
3159         {name: "ddd", func: func},
3160         {name: "eee", func: func},
3161         {name: "test", func: func},
3162     ]),
3163 
3164     // 添加到 root / name: test 的后代
3165     menuViewA = root.add(new MenuView([
3166         {name: "AAA", func: func},
3167         {name: "bbb", func: func},
3168         {name: "ccc", func: func},
3169         {name: "ddd", func: func},
3170     ]), "test"|5);
3171 
3172     menuViewA.getViewByName("ddd").lcon_def.set(0|1|2); //外部修改样式
3173 
3174 */
3175 class MenuView extends TreeStruct{
3176 
3177     static sign = "";
3178     static parentElem = null;
3179     static className = "";
3180     static textSize = 12;
3181     static lconSize = 16;
3182     static borderSize = 0; //如果小于或等于零不创建边框
3183     static textColor = {down: "#0000ff", up: "#000000", disable: "#666666"};
3184     static rowBorderColor = {down: "#ffffff", up: "#000000", disable: "#666666"};
3185     static rowBackgroundColor = {down: "rgba(0,0,255,0.2)", up: "#ffffff", disable: "#999999"}; //如果为null不创建背景
3186     static rowPadding = {top: 2, right: 2, bottom: 2, left: 2};
3187     static rowMargin = {top: 0, right: 0, bottom: 0, left: 0};
3188     static defaultLcon = "min";
3189     static buttonType = -1;
3190 
3191     static reset(){ //重置上面静态属性的值
3192         MenuView.sign = "";
3193         MenuView.parentElem = null;
3194         MenuView.className = "";
3195         MenuView.textSize = 12;
3196         MenuView.lconSize = 16;
3197         MenuView.borderSize = 0;
3198 
3199         MenuView.textColor.down = "#0000ff";
3200         MenuView.textColor.up = "#000000";
3201         MenuView.textColor.disable = "#666666";
3202 
3203         MenuView.rowBorderColor.down = "#ffffff";
3204         MenuView.rowBorderColor.up = "#000000";
3205         MenuView.rowBorderColor.disable = "#666666";
3206 
3207         MenuView.rowBackgroundColor.down = "rgba(0,0,255,0.2)";
3208         MenuView.rowBackgroundColor.up = "#ffffff";
3209         MenuView.rowBackgroundColor.disable = "#999999";
3210 
3211         MenuView.rowPadding.top = 2;
3212         MenuView.rowPadding.right = 2;
3213         MenuView.rowPadding.bottom = 2;
3214         MenuView.rowPadding.left = 2;
3215 
3216         MenuView.rowMargin.top = 0;
3217         MenuView.rowMargin.right = 0;
3218         MenuView.rowMargin.bottom = 0;
3219         MenuView.rowMargin.left = 0;
3220 
3221         MenuView.defaultLcon = "min";
3222         MenuView.buttonType = -1;
3223     
3224     }
3225 
3226     static _cac = new CanvasAnimateCustom();
3227     static nowView = null;
3228     static clearNowView(){
3229         if(MenuView.nowView === null) return;
3230         MenuView.nowView.traverseUp(v => v.visible = false);
3231         MenuView.nowView = null;
3232 
3233     }
3234 
3235     #length = 0;
3236     #visible = false;
3237 
3238     get top(){
3239         return this.view.domElementRect.y;
3240     }
3241 
3242     get left(){
3243         return this.view.domElementRect.x;
3244     }
3245 
3246     get width(){
3247         return this.view.box.w;
3248     }
3249 
3250     get height(){
3251         return this.view.box.h;
3252     }
3253 
3254     get visible(){
3255         return this.#visible;
3256     }
3257 
3258     set visible(v){
3259         if(v === true){
3260             this.update(); //更新自己及自己第一层后代位置 
3261             this.#visible = true;
3262             this.view.domElement.style.visibility = "visible";
3263         }
3264 
3265         else if(v === false){
3266             this.#visible = false;
3267             this.view.domElement.style.visibility = "hidden";
3268 
3269             //hidden root
3270             if(this.parent === null){
3271                 this.traverse(v => {
3272                     if(v && v.visible !== false) v.visible = false;
3273                     
3274                 });
3275         
3276             }
3277 
3278         }
3279         
3280     }
3281 
3282     constructor(names, funcs, lcons, parentElem){
3283         super();
3284         
3285         this.names = names || [];
3286         this.funcs = funcs || null;
3287         this.lcons = lcons || null;
3288         this.parentElem = parentElem || MenuView.parentElem || document.body;
3289 
3290         this.view = new CanvasAnimateRender({className: MenuView.className});
3291         this.events = new CanvasAnimateEvent(this.view);
3292         this.views = [];
3293         //this._eventTargets = [];
3294         this.parentElemRect = null;
3295         
3296         if(typeof names[0] !== "string") this.importData(names);
3297         this.initView(this.names);
3298 
3299     }
3300 
3301     _traverse(callback, key){
3302         if(callback(this, key) !== "continue"){
3303             for(let k = 0; k < this.#length; k++){
3304                 if(this.children[k]) this.children[k].traverse(callback, k);
3305                 else callback(null, k, this);
3306     
3307             }
3308 
3309         }
3310 
3311     }
3312 
3313     traverse(callback){
3314         this._traverse(callback, this.parent === null ? -1 : this.parent.children.indexOf(this));
3315     }
3316 
3317     getViewByName(name){
3318         const k = this.names.indexOf(name);
3319         if(k !== -1) return this.views[k];
3320     }
3321 
3322     add(v, k){
3323 
3324         v.parent = this;
3325 
3326         if(this.children.includes(v) === false){
3327 
3328             if(k === undefined) this.children.push(v);
3329 
3330             else{
3331 
3332                 if(typeof k === "string") k = this.names.indexOf(k);
3333 
3334                 if(this.children[k]) this.children[k].exit();
3335                 
3336                 this.children[k] = v;
3337                 
3338             }
3339 
3340         }
3341 
3342         if(v.parentElem !== this.parentElem){
3343             v.parentElem = this.parentElem;
3344             v.parentElemRect = this.parentElemRect;
3345         }
3346         
3347         return v;
3348     }
3349 
3350     exit(){
3351 
3352         //清理 MenuView.nowView
3353         MenuView.clearNowView();
3354 
3355         this.traverse(v => {
3356             if(!v) return;
3357 
3358             //清理.view
3359             if(v.view.domElement.parentElement) v.view.domElement.parentElement.removeChild(v.view.domElement);
3360 
3361             //清理.events (over out 事件)
3362             v.events.disposeEvent();
3363             /* for(let k = 0, len = v._eventTargets.length; k < len; k++){
3364                 v.events.clear(v._eventTargets[k], "over");
3365                 v.events.clear(v._eventTargets[k], "out");
3366             } */
3367             
3368         });
3369 
3370         //从树结构中移出
3371         if(this.parent !== null) this.parent.remove(this);
3372 
3373         this.views.length = 0;
3374 
3375     }
3376 
3377     _update(v, k, p){
3378         
3379         //child hidden
3380         if(!v){
3381             const child = p.views[k].child;
3382             if(child.visible !== false){
3383                 child.visible = false;
3384                 p.view.redraw();
3385             }
3386             
3387             return;
3388         }
3389 
3390         if(v.parent === null) return;
3391 
3392         if(v.parent.parentElem !== v.parentElem) v.parentElem = v.parent.parentElem;
3393         if(v.parent.parentElemRect !== v.parentElemRect) v.parentElemRect = v.parent.parentElemRect;
3394 
3395         //child visible
3396         const child = v.parent.views[k].child;
3397         if(child.visible !== true){
3398             child.visible = true;
3399             v.parent.view.redraw();
3400         }
3401         
3402         //canvas position (bug: 不能直接 实时的引用 MenuView 的静态属性, 最好在初始化视图时缓存所需的静态属性)
3403         const x = v.parent.left + v.parent.width - MenuView.rowPadding.left - MenuView.rowMargin.left;
3404         if(x + v.view.box.w > v.parent.parentElemRect.x + v.parent.parentElemRect.width) v.view.pos(v.parent.left - v.width, (MenuView.rowPadding.top + MenuView.rowPadding.bottom + MenuView.lconSize) * k + MenuView.rowMargin.top * k + MenuView.rowMargin.top +  v.parent.top);
3405         else v.view.pos(x, (MenuView.rowPadding.top + MenuView.rowPadding.bottom + MenuView.lconSize) * k + MenuView.rowMargin.top * k + MenuView.rowMargin.top +  v.parent.top);
3406 
3407     }
3408 
3409     update(){ //更新自己第一层后代 (如果更新所有后代: this.traverse(this._update))
3410         if(this.parent === null){
3411             if(this.parentElemRect === null) this.parentElemRect = {x: 0, width: 0};
3412             const rect = this.parentElem.getBoundingClientRect();
3413             this.parentElemRect.x = rect.x;
3414             this.parentElemRect.width = rect.width;
3415         }
3416 
3417         else{
3418             if(this.parent.parentElem !== this.parentElem) this.parentElem = this.parent.parentElem;
3419             if(this.parent.parentElemRect !== this.parentElemRect) this.parentElemRect = this.parent.parentElemRect;
3420             this._update(this, this.parent.children.indexOf(this));
3421         }
3422 
3423         for(let k = 0; k < this.#length; k++) this._update(this.children[k], k, this);
3424         
3425     }
3426 
3427     _setHiddenOrChild(k){
3428         if(k !== -1 && this.parent !== null && this.parent.views[k]){
3429             const obj = this.parent.views[k];
3430             if(obj.disable === false){
3431                 if(obj.child !== null) obj.child.set(1);
3432                 if(obj.background !== null) obj.background.set(1);
3433                 if(obj.border !== null) obj.border.set(1);
3434                 obj.name.set(1);
3435                 this.parent.view.redraw();
3436             }
3437             
3438         }
3439 
3440         if(this.#visible !== false) this.visible = false;
3441 
3442     }
3443 
3444     clearNowView(k){
3445         if(MenuView.nowView !== null && MenuView.nowView !== this && MenuView.nowView !== this.children[k]){
3446 
3447             if(MenuView.nowView.parent === this) MenuView.nowView._setHiddenOrChild(MenuView.nowView.parent === null ? -1 : MenuView.nowView.parent.children.indexOf(MenuView.nowView));
3448 
3449             else{
3450 
3451                 let tar = null;
3452 
3453                 MenuView.nowView.traverse((v, key) => {
3454 
3455                     if(v){
3456                         if(v !== this && v !== this.children[k]) v._setHiddenOrChild(key);
3457                         else tar = v;
3458                     }
3459                     
3460                 });
3461                 
3462                 if(tar === null){
3463 
3464                     MenuView.nowView.traverseUp(v => {
3465 
3466                         if(v !== this && v !== this.children[k]) v._setHiddenOrChild(v.parent === null ? -1 : v.parent.children.indexOf(v));
3467                         else return "break";
3468 
3469                     });
3470 
3471                 }
3472 
3473             }
3474         
3475         }
3476 
3477     }
3478 
3479     importData(data){
3480         data = data || this.names;
3481         if(Array.isArray(data) === false) return console.warn("MenuView: 导入失败");
3482         this.names = [];
3483 
3484         for(let k = 0, len = data.length; k < len; k++){
3485             if(!data[k] || typeof data[k].name !== "string") return console.warn("MenuView: 导入失败");
3486 
3487             this.names.push(data[k].name);
3488 
3489             if(typeof data[k].func === "function"){
3490                 if(this.funcs === null) this.funcs = [];
3491                 this.funcs[k] = data[k].func;
3492             }
3493 
3494             if(data[k].lcon){
3495                 if(this.lcons === null) this.lcons = [];
3496                 this.lcons[k] = data[k].lcon;
3497             }
3498 
3499         }
3500 
3501     }
3502 
3503     initView(names){
3504         names = names || this.names;
3505         this.#length = names.length;
3506         if(Array.isArray(names) === false || this.#length === 0) return console.warn("MenuView: 初始化失败");
3507         
3508         const th = this, size = MenuView.textSize, lconSize = MenuView.lconSize, borSize = MenuView.borderSize, defaultLcon = MenuView.defaultLcon, buttonType = MenuView.buttonType;
3509 
3510         var _cac = MenuView._cac, _path = [1, 1, 1, lconSize-1, lconSize-1, lconSize/2],
3511         color = MenuView.textColor, bgColor = MenuView.rowBackgroundColor, 
3512         borColor = MenuView.rowBorderColor, padding = MenuView.rowPadding, margin = MenuView.rowMargin,
3513         maxTextWidth = 0, maxWidth = 0, maxHeight = 0;
3514 
3515         //set maxTextWidth
3516         for(let k = 0; k < this.#length; k++){
3517             _cac.box.set(0, 0, 0, 0);
3518             _cac.text(names[k], "", size);
3519             if(_cac.box.w > maxTextWidth) maxTextWidth = _cac.box.w;
3520         }
3521 
3522         //initView
3523         for(let k = 0; k < this.#length; k++){
3524             const _h = padding.top + padding.bottom + lconSize, //
3525             _y = _h * k + margin.top * k + margin.top,
3526             bgW = padding.left + padding.right + maxTextWidth + lconSize * 2 + 4;
3527 
3528             //background
3529             let bg = null;
3530             if(bgColor !== null){
3531                 bg = new CanvasAnimateImages().pos(margin.left, _y);
3532                 _cac.size(bgW, _h).rect();
3533                 _cac.context.lineWidth = borSize || 1;
3534                 bg.setImage(_cac.clear().fill(bgColor.disable).shear());
3535                 bg.setImage(_cac.clear().fill(bgColor.up).shear());
3536                 bg.setImage(_cac.clear().fill(bgColor.down).shear());
3537 
3538                 bg.set(1);
3539                 this.view.list.push(bg);
3540             }
3541 
3542             //defined lcons: "✘", "✔", "●", "◆", "■", "★"
3543             let lcon_def = null;
3544             if(defaultLcon !== ""){
3545                 _cac.size(lconSize, lconSize);
3546 
3547                 if(defaultLcon === "max") lcon_def = new CanvasAnimateImages([
3548                     _cac.clear().text("✘", color.disable, size, "center", "center").shear(),
3549                     _cac.clear().text("✔", color.down, size, "center", "center").shear(),
3550                     _cac.clear().text("●", color.down, size, "center", "center").shear(),
3551                     _cac.clear().text("◆", color.down, size, "center", "center").shear(),
3552                     _cac.clear().text("■", color.down, size, "center", "center").shear(),
3553                     _cac.clear().text("★", color.down, size, "center", "center").shear()
3554                 ]);
3555                     
3556                 else lcon_def = new CanvasAnimateImages([
3557                     _cac.clear().text("✘", color.disable, size, "center", "center").shear(),
3558                     _cac.clear().text("✔", color.down, size, "center", "center").shear(),
3559                     _cac.clear().text("●", color.down, size, "center", "center").shear()
3560                 ]);
3561 
3562                 lcon_def.pos(margin.left + padding.left, _y + padding.top);
3563                 this.view.list.push(lcon_def);
3564 
3565             }
3566 
3567             //lcons
3568             let lcon = null;
3569             if(Array.isArray(this.lcons) === true && Array.isArray(this.lcons[k]) === true){
3570                 lcon = new CanvasAnimateImages(this.lcons[k]).pos(margin.left + padding.left, _y + padding.top);
3571                 lcon.set(0);
3572                 this.view.list.push(lcon);
3573 
3574             }
3575 
3576             //names
3577             _cac.size(maxTextWidth, lconSize);
3578             const str = new CanvasAnimateImages([
3579                 _cac.clear().text(names[k], color.disable, size, "", "center").shear(),
3580                 _cac.clear().text(names[k], color.up, size, "", "center").shear(),
3581                 _cac.clear().text(names[k], color.down, size, "", "center").shear(),
3582                 
3583             ]).pos(margin.left + padding.left + lconSize + 2, _y + padding.top);
3584             str.set(1);
3585             this.view.list.push(str);
3586 
3587             //child
3588             _cac.size(lconSize, lconSize).path(_path, true);
3589             const child = new CanvasAnimateImages([
3590                 _cac.clear().stroke(color.disable, 1).shear(),
3591                 _cac.clear().fill(color.up, 1).shear(),
3592                 _cac.clear().stroke(color.down, 1).shear(),
3593                 
3594             ]).pos(str.box.x + maxTextWidth + 2, _y + padding.top);
3595             
3596             child.set(1);
3597             this.view.list.push(child);
3598 
3599             //border
3600             let bor = null;
3601             if(borSize > 0){
3602                 bor = new CanvasAnimateImages().pos(margin.left, _y);
3603                 _cac.size(bgW, _h).rect();
3604                 bor.setImage(_cac.clear().stroke(borColor.disable, borSize).shear());
3605                 bor.setImage(_cac.clear().stroke(borColor.up, borSize).shear());
3606                 bor.setImage(_cac.clear().stroke(borColor.down, borSize).shear());
3607                 bor.set(1);
3608                 this.view.list.push(bor);
3609             }
3610 
3611             //views
3612             let _disable = false, i_lcon = -1, i_lcon_def = -1;
3613             this.views.push({
3614                 background: bg,
3615                 lcon_def: lcon_def,
3616                 lcon: lcon,
3617                 name: str,
3618                 child: child,
3619                 border: bor,
3620                 key: k,
3621                 target: this,
3622                 get disable(){
3623                     return _disable;
3624                 },
3625                 set disable(v){
3626                     if(v === true){
3627                         th.clearNowView(k);
3628                         if(th.children[k]) th.children[k].visible = false;
3629                         if(lcon_def !== null){
3630                             i_lcon_def = lcon_def.i;
3631                             lcon_def.set(0);
3632                         }
3633                         if(lcon !== null){
3634                             i_lcon = lcon.i;
3635                             lcon.set(-1);
3636                         }
3637                         if(bg !== null) bg.set(0);
3638                         if(bor !== null) bor.set(0);
3639                         str.set(0);
3640                         child.set(0);
3641                         _disable = true;
3642                     }
3643 
3644                     else if(v === false){
3645                         if(lcon_def !== null) lcon_def.set(i_lcon_def);
3646                         if(lcon !== null) lcon.set(i_lcon);
3647                         if(bg !== null) bg.set(1);
3648                         if(bor !== null) bor.set(1);
3649                         str.set(1);
3650                         child.set(1);
3651                         _disable = false;
3652                     }
3653                     
3654                 },
3655             });
3656 
3657             //maxWidth, maxHeight
3658             const mw = bgW + margin.left + margin.right;
3659             if(maxWidth < mw) maxWidth = mw;
3660             const mh = _y + _h + margin.bottom;
3661             if(maxHeight < mh) maxHeight = mh;
3662 
3663             //event
3664             const eventTarget = bor || bg || str;
3665             //this._eventTargets.push(eventTarget);
3666 
3667             if(Array.isArray(this.funcs) === true){
3668                 let _isDown = false, pointerId = -1;
3669                 this.events.add(eventTarget, "down", event => {
3670                     if(_disable === true || (buttonType !== -1 && event.button !== buttonType)) return _isDown = false;
3671                     
3672                     if(!this.children[k]){
3673                         if(bg !== null) bg.set(2);
3674                         if(bor !== null) bor.set(2);
3675                         this.view.redraw();
3676                     }
3677                     
3678                     pointerId = event.pointerId;
3679                     _isDown = true;
3680                 });
3681     
3682                 this.events.add(eventTarget, "up", event => {
3683                     if(pointerId !== event.pointerId || _isDown === false || _disable === true || (buttonType !== -1 && event.button !== buttonType)) return;
3684                     pointerId = -1;
3685                     if(!this.children[k]){
3686                         if(bg !== null) bg.set(1);
3687                         if(bor !== null) bor.set(1);
3688                     }
3689                     
3690                     if(typeof this.funcs[k] === "function") this.funcs[k](this.views[k], event);
3691                     this.view.redraw();
3692                 });
3693 
3694             }
3695         
3696             this.events.add(eventTarget, "over", () => {
3697                 if(_disable === true) return;
3698                 if(this.children[k]){
3699                     if(bg !== null) bg.set(2);
3700                     if(bor !== null) bor.set(2);
3701                 }
3702                 str.set(2);
3703                 
3704                 this.clearNowView(k);
3705                 if(this.children[k] !== undefined){
3706                     MenuView.nowView = this.children[k];
3707                     MenuView.nowView.visible = true;
3708                     child.set(2);
3709                 }
3710 
3711                 this.view.redraw();
3712             });
3713 
3714             this.events.add(eventTarget, "out", () => {
3715                 if(_disable === true) return;
3716 
3717                 if(this.children[k]){
3718                     if(bg !== null) bg.set(1);
3719                     if(bor !== null) bor.set(1);
3720                 }
3721                 str.set(1);
3722                 this.view.redraw();
3723                 
3724             });
3725 
3726         }
3727         
3728         //init this.view
3729         this.view.initList();
3730         this.view.size(maxWidth, maxHeight);
3731         this.view.domElement.style.backgroundColor = bgColor.up;
3732         this.view.domElement.style.position = "absolute";
3733         this.view.render(this.parentElem);
3734         this.visible = this.#visible;
3735         if(MenuView.sign !== ""){
3736             if(this.view.domElement[MenuView.sign] === undefined) this.view.domElement[MenuView.sign] = true;
3737             else console.warn("MenuView: 标记失败, 属性名重复 "+MenuView.sign);
3738         }
3739         
3740         _cac = color = bgColor = borColor = padding = margin = null;
3741         
3742     }
3743 
3744 }
3745 
3746 
3747 
3748 
3749 /* ImageViewer 图片查看器
3750 
3751 parameter:
3752     option: {
3753         defaultEvent: Bool; //默认true
3754     };
3755 
3756 attribute:
3757     image: CanvasImage;
3758     scale: Number; //小于1缩小, 大于1放大
3759 
3760 method:
3761     center(): this; //图片在视口居中
3762     setViewportScale: this; //将图片按比例缩放至视口大小
3763     setScale(v): this; //设置.scale
3764     setImage(image): this; //设置.image
3765     drawImage(): this; //更新图像 (之后需要.redraw()更新画布)
3766 
3767 demo:
3768     const imageViewer = new ImageViewer({
3769         width: this.width, 
3770         height: this.width, 
3771         className: 'shadow-convex',
3772     });
3773 
3774     imageViewer.domElement.style = `
3775         background-color: rgb(127,127,127);
3776         border-radius: 4px;
3777         z-index: 99999;
3778         position: absolute;
3779     `;
3780 
3781     imageViewer.pos(100, 100).setImage(image)
3782     .setViewportScale().center()
3783     .drawImage().render();
3784 
3785 */
3786 class ImageViewer extends CanvasAnimateRender{
3787 
3788     //允许图片的最大宽高 (min 不能小于1)
3789     #min = 1;
3790     #max = 4096; 
3791 
3792     constructor(option){
3793         super(option);
3794 
3795         this.eventDispatcher = new CanvasAnimateEvent(this);
3796         this.target = this.add(new CanvasAnimateCustom()).size(this.box.w, this.box.h);
3797 
3798         this.image = null;
3799         this.scale = 1;
3800         
3801         this._rangeMin = 0;
3802         this._rangeMax = 0;
3803         this._box = new Box();
3804         this._oldWidth = this._box.w;
3805 
3806         if(option.defaultEvent === false) return this;
3807 
3808         //拖拽事件
3809         var tzX = 0, tzY = 0;
3810         const onMove = event => {
3811             this._box.pos(event.pageX - this.domElementRect.x - tzX, event.pageY - this.domElementRect.y - tzY);
3812             this.drawImage().redraw();
3813 
3814         },
3815 
3816         onUp = () => {
3817             document.body.removeEventListener("pointerup", onUp);
3818             document.body.removeEventListener("pointermove", onMove);
3819 
3820             this.eventDispatcher.remove(this.target, 'up', onUp);
3821             this.eventDispatcher.remove(this.target, 'move', onMove);
3822             
3823         }
3824 
3825         this.eventDispatcher.add(this.target, "down", event =>{
3826             onUp();
3827 
3828             if(this.image === null) return;
3829 
3830             tzX = event.offsetX - this._box.x;
3831             tzY = event.offsetY - this._box.y;
3832             
3833             this.eventDispatcher.add(this.target, 'up', onUp);
3834             this.eventDispatcher.add(this.target, 'move', onMove);
3835             
3836             document.body.addEventListener("pointerup", onUp);
3837             document.body.addEventListener("pointermove", onMove);
3838 
3839         });
3840 
3841         //缩放事件 (以鼠标为中心点缩放)
3842         this.eventDispatcher.add(this.target, "wheel", event =>{
3843 
3844             const oldWidth = this._box.w;
3845             this.setScale(event.wheelDelta === 120 ? this.scale - this.scale * 0.5 : this.scale + this.scale * 0.5);
3846 
3847             const ratio = this._box.w / oldWidth,
3848             nx = event.offsetX - ((event.offsetX - this._box.x) * ratio + this._box.x) + this._box.x,
3849             ny = event.offsetY - ((event.offsetY - this._box.y) * ratio + this._box.y) + this._box.y;
3850             
3851             this._box.pos(nx, ny);
3852             this.drawImage().redraw();
3853 
3854         });
3855 
3856         //旋转事件
3857 
3858 
3859         this._onUp = onUp;
3860 
3861     }
3862 
3863     exit(){
3864         if(this._onUp) this._onUp();
3865         if(this.domElement.parentElement) this.domElement.parentElement.removeChild(this.domElement);
3866 
3867     }
3868     
3869     size(w, h, setElem){
3870         super.size(w, h, setElem);
3871         this.target.size(this.box.w, this.box.h);
3872 
3873         return this;
3874     }
3875     
3876     center(){
3877         this._box.pos((this.box.w - this._box.w)/2, (this.box.h - this._box.h)/2);
3878 
3879         return this;
3880         
3881     }
3882 
3883     setViewportScale(){
3884         if(this.image === null) return this;
3885         const width = this.image.width, ratio = width / this.image.height;
3886         
3887         return this.setScale(ratio < 1 ? ratio * this.box.w / width : this.box.w / width);
3888     }
3889 
3890     setScaleAt(x, y){ //x, y 是相对于画布位置
3891         const ratio = this._box.w / this._oldWidth,
3892         nx = x - ((x - this._box.x) * ratio + this._box.x) + this._box.x,
3893         ny = y - ((y - this._box.y) * ratio + this._box.y) + this._box.y;
3894         
3895         this._box.pos(nx, ny);
3896 
3897     }
3898 
3899     setScale(v){
3900         if(v === Infinity || isNaN(v) === true || typeof v !== "number") return this;
3901         this.scale = v < this._rangeMin ? this._rangeMin : v > this._rangeMax ? this._rangeMax : v;
3902         if(this.image !== null){
3903             this._box.size(this.image.width * this.scale, this.image.height * this.scale);
3904             this._oldWidth = this._box.w;
3905         }
3906         return this;
3907     }
3908 
3909     setImage(image){
3910 
3911         if(this.isCanvasImage(image) === true){
3912             
3913             if(image.width > image.height){
3914                 this._rangeMin = this.#min / image.width;
3915                 this._rangeMax = this.#max / image.width;
3916 
3917             }
3918 
3919             else{
3920                 this._rangeMin = this.#min / image.height;
3921                 this._rangeMax = this.#max / image.height;
3922 
3923             }
3924 
3925             this.image = image;
3926 
3927         }
3928 
3929         else if(this.image !== null){
3930 
3931             this.image = null;
3932             this.target.clear();
3933             this.redraw();
3934 
3935         }
3936 
3937         return this;
3938     }
3939 
3940     drawImage(){
3941 
3942         if(this.image !== null) this.target.clear().context.drawImage(this.image, this._box.x, this._box.y, this._box.w, this._box.h);
3943 
3944         return this;
3945     }
3946 
3947 }
3948 
3949 
3950 
3951 
3952 /* CanvasAnimateUI UI控件
3953 依赖:
3954     ImageViewer
3955     MenuView
3956     ColorTestViewer
3957 
3958 注意:
3959     传入的 .target 和 .data: CanvasUI不污染其内容(既只读), 所以可以重复利用;
3960     一旦初始化完UI后(.initUI()) 修改.data属性对控件运行没任何影响;
3961     如果想修改 Euler 控件的 range.step 点input框输入: step: 0.01; (两边或冒号两边可以有空格)
3962 
3963 支持的类型: 
3964     string    (文本控件)
3965     color    (颜色控件)
3966     number    (数值控件)
3967     boolean    (复选框控件)
3968     button    (按钮控件)
3969     object    (Point, Vector2, Vector3, Euler, Color, RGBColor, CanvasImageData), //暂不支持: Vector4, Box
3970 
3971     //以下控件其属性值可以是任意类型
3972     select    (单选控件, 定义.selectList 显示声明)
3973     file    (导入控件, .type = json, image 显示声明)
3974 
3975     //特殊
3976     line    (分割线, .type = 'line'; .value = valueUrl|Array[valueUrl])
3977 
3978 parameter(target, data, parentElem, option)
3979     target: Object;    //
3980 
3981     data: Array[
3982         {
3983             //必须:
3984             valueUrl: string, //路径,忽略所有的空格;
3985             例(.链接对象; []链接数组; 不可以混淆): 
3986             target = {name: 'name', arr:[10], obj: null}; 
3987             data = [
3988                 {valueUrl: '.name'},
3989                 {valueUrl: '.arr[0]'},
3990                 {valueUrl: '.obj.name'} //此valueUrl视为 undefined (既忽略此条)
3991             ]
3992             
3993             //可选:
3994             type: String; //可能的值: line, image, json, select
3995             title: String; //标题
3996             explain: String; //详细说明
3997             update: boolean //this.update() 是否可以更新此控件 (注意优先级高于 this.globalUpdate)
3998             disable: boolean //启用或禁用控件(可以通过.onChange 的参数.disable属性随时调整此控件的启用和禁用)
3999             onChange: Function(Object{ //控件使属性值改变时触发
4000                 data:        Object,
4001                 scope:         CanvasAnimateUI,            //
4002                 update:     Function|Array[Function],     //使此控件主动读一次value值,但不更新视图;(配合.redraw 把value传递至控件)
4003                 redraw:     Function,                    //只绘制此控件; obj.target.value = 123; obj.update(); obj.redraw();
4004                 disable:     Boolean,                    //启用或禁用此控件
4005                 target:     Object{value},                //target.object: 是valueUrl的上一层引用, 如果只有一层则等于 CanvasAnimateUI.target
4006                 valueName:    String,                        //此属性只属于数字控件
4007             }), 
4008 
4009             range: object{min, max, step: Number}, //数字控件的范围 默认 this.defiendRange
4010             selectList: Array[Object{name:String, value:任意值}], //显示指定为select 或 type = select;
4011             value: valueUrl || Array[valueUrl], //type 为 line 时才有效; (只要其中某一个 valueUrl 指向的值不等于undefined 则渲染此line; 如果全都等于undefined 或 紧挨的上层也是线 将忽略此line)
4012 
4013         }
4014     ]
4015 
4016     parentElem: DomElement;    //父容器 默认null
4017 
4018     option = {
4019         autoHeight: Bool        //是否自动设置可视高 默认 false
4020         width, height,            //可视宽高
4021         eachHeight,                //每项的高 默认 30
4022         margin,                 //边距 默认 4
4023         titleColor, conColor,    //颜色 默认#000
4024         fontSize,                 //字体大小 默认 12 (建议不超过 .eachHeight)
4025         globalUpdate,             // 默认 false
4026         onChanges,                // 默认 null
4027         defiendRange,            //默认 {min: -999999, max: 999999, step: 1};
4028         numberCursorSize        //数字控件的游标大小比例, 值为 0 - 1 之间; 默认 0.5
4029         style: String            //如果定义则使用.style(style)
4030         
4031     }
4032 
4033 attribute:
4034     target: Object;             //默认null
4035     data: Array;                 //默认null
4036     onChanges: Function(Object); //默认null
4037     defiendRange: Object;         //默认{min: -999999, max: 999999, step: 1};
4038     parent: DomElement;            //默认 body
4039     globalUpdate: Boolean;        //默认false; 如果为true则创建的所有控件都可以在.update() 里面更新; update:false的控件除外
4040 
4041 method:
4042     style(style: String): this;            //添加css样式 (width, height, padding 属性会被覆盖, 可以通过构造器的参数设置这些值)
4043     pos(left, top: Number): this;        //设置位置(前提设定了css定位)
4044     render(parentElem): this;            //添加到dom树
4045     initUI(target, data): this;            //初始化控件
4046     update(redraw: Bool): undefined;     //更新所有已定义为更新(data.update = true)的控件
4047     getView(data): Object;                 //根据所给的data获取 可操控的对象(假设 this.update() 可以更新此data代表的控件); 返回的对象恒等于 .onChange 的参数
4048     getData(valueUrl): Object;             //获取data
4049     
4050 demo:
4051 
4052     const splitLineOfNumber = {type: 'line', value: [".num", ".vector2", '.vector3']},
4053 
4054     selectList = [
4055         {name: "ZeroZeroZeroZeroZero", value: 0},
4056         {name: "OneOneOneOneOne", value: 1},
4057         {name: "TwoTwoTwoTwoTwo", value: 2},
4058         {name: "ThreeThreeThreeThreeThree", value: 3},
4059     ],
4060 
4061     target = {
4062         str: "字符串String",
4063         checkbox: true,
4064         func: v=>console.log(v),
4065         select: 1,
4066         image: null,
4067 
4068         colorHEX: " # f0 0f0 f",
4069         colorRGB: "rgba(11, 22, 255, 1)",
4070         color: "  b l u e ",
4071         colorRGB: new RGBColor(),
4072         colorTHREE: new THREE.Color(),
4073 
4074         num: 0,
4075         vector2: new THREE.Vector2(30, 70),
4076         vector3: new THREE.Vector3(0, 30, 70),
4077 
4078     },
4079 
4080     data = [
4081         {
4082             title: "文本",
4083             valueUrl: ".str",
4084         },
4085 
4086         {
4087             title: "函数",
4088             valueUrl: ".func",
4089         },
4090 
4091         {
4092             title: "颜色",
4093             valueUrl: ".color",
4094         },
4095 
4096         {
4097             title: "复选框",
4098             valueUrl: ".checkbox",
4099         },
4100 
4101         {
4102             title: "单选",
4103             valueUrl: ".select",
4104             selectList: selectList,
4105         },
4106 
4107         //分割线 开始
4108         splitLineOfNumber,
4109         {
4110             title: "数字",
4111             explain: '游标的范围可在range范围内调整',
4112             valueUrl: ".num",
4113             range: {min: -10, max: 10, step: 0.1},
4114             update: true,
4115             onChange: v => console.log(v),
4116         },
4117 
4118         {
4119             title: "坐标2",
4120             valueUrl: ".vector2",
4121         },
4122         splitLineOfNumber,
4123         //分割线 结束
4124 
4125         {
4126             title: "图片",
4127             valueUrl: ".image",
4128             type: "image",
4129         },
4130 
4131     ],
4132 
4133     cau = new CanvasAnimateUI(target, data, null, {
4134         width: 300, 
4135         height: 210,    //可视宽高
4136         eachHeight: 30,    //每项的高
4137         fontSize: 12,     //字体大小
4138         defiendRange: {min: 0, max: 100, step: 1},
4139     })
4140 
4141     .style(`
4142         z-index: 9999;
4143         position: absolute;
4144         left: 200px;
4145         top: 190px;
4146         background: #fff;
4147     `)
4148 
4149     .initUI()
4150     .render();
4151 
4152     //test
4153     new Timer(()=>{
4154         const obj = cau.getView(cau.getData('.num'));
4155         obj.disable = true;
4156         obj.target.value = 0.5;
4157         obj.update();
4158         obj.redraw();
4159         console.log(obj);
4160     }, 5000).start();
4161 
4162 */
4163 class CanvasAnimateUI extends CanvasAnimateRender{
4164 
4165     #i = 0;
4166     #h = 0;
4167     #isLine = false;
4168 
4169     get width(){
4170         return this.box.w;
4171     }
4172 
4173     get height(){
4174         return this.box.h;//return this.domElement.height;
4175     }
4176 
4177     get clientWidth(){
4178         return this.box.w;
4179     }
4180 
4181     get clientHeight(){
4182         return this._ch;
4183     }
4184 
4185     static emptyCAC = new CanvasAnimateCustom();
4186     static emptyTimerA = new Timer(null, 500, Infinity, false);
4187     static emptyFunc(){}
4188     static stopTimers(){
4189         CanvasAnimateUI.emptyTimerA.stop();
4190         CanvasAnimateUI.emptyTimerA.speed = 500;
4191     }
4192 
4193     constructor(target = null, data = null, parentElem = null, option = {}){
4194         super(option);
4195         
4196         this.autoHeight = typeof option.autoHeight === 'boolean' ? option.autoHeight : false;
4197         this.target = target;
4198         this.data = data;
4199         this.globalUpdate = typeof option.globalUpdate === "boolean" ? option.globalUpdate : false;
4200         this.onChanges = option.onChanges || null;
4201         this.defiendRange = option.defiendRange || {min: -999999, max: 999999, step: 1};
4202         //this.readValueUrlType = option.readValueUrlType || "";
4203         
4204         this._margin = option.margin || 4;
4205         this._height = option.eachHeight || 30;
4206         this._titleColor = option.titleColor || "#000";
4207         this._conColor = option.conColor || "#000";
4208         this._titleWidth = this.width * 0.25;
4209         this._conWidth = this.width * 0.75;
4210         this._fontSIze = option.fontSize || 12;
4211         this._stringMaxCount = Math.floor(this._conWidth / this._fontSIze) * 2;
4212         this._funcTitleOut = ()=>this.domElement.title = "";
4213         this._numberInputWidth = 0;
4214         this._ch = this.box.h;
4215         
4216         //this._lines = [];
4217         this._menuViews = [];
4218         this._loopData = [];
4219         this._loopObject = [];
4220         this._globalCA = new CanvasAnimate();
4221 
4222         this.cae = new CanvasAnimateEvent(this);
4223         this.imageViewer = new ImageViewer({width: this.width, height: this.width});
4224         this.colorTestView = new ColorTestViewer({width: this._conWidth});
4225         this.parent = this._getParent();
4226         this._input = this._getInput();
4227         this._inputFunc = null;
4228         
4229         this._initImages(option.numberCursorSize);
4230 
4231         if(parentElem !== null) this.render(parentElem);
4232         this._styleShadowConvex(this.imageViewer.domElement);
4233         this._styleShadowConvex(this.colorTestView.domElement);
4234         if(typeof option.style === 'string') this.style(option.style);
4235         
4236     }
4237 
4238     exit(){
4239         this.cae.disposeEvent(); //event
4240         for(let i = 0, c = this._menuViews.length; i < c; i++) this._menuViews[i].exit(); //this._menuViews
4241         this.imageViewer.exit(); //this.imageViewer
4242         this.colorTestView.exit(); //this.colorTestView
4243         if(this._input.parentElement) this._input.parentElement.removeChild(this._input); //this._input
4244         if(this.parent.parentElement) this.parent.parentElement.removeChild(this.parent); //this.parent
4245 
4246     }
4247 
4248     render(parentElem){
4249         if(!parentElem) parentElem = document.body;
4250         parentElem.appendChild(this.parent);
4251         super.updateCanvas();
4252 
4253         this.imageViewer.domElement.style = `
4254             background-color: rgb(127,127,127);
4255             border-radius: 4px;
4256             z-index: 99999;
4257             display: none;
4258             position: absolute;
4259         `;
4260 
4261         this.colorTestView.domElement.style = `
4262             background-color: #fff;
4263             border-radius: 4px;
4264             z-index: 99999;
4265             display: none;
4266             position: absolute;
4267         `;
4268 
4269         this.imageViewer.render();
4270         this.colorTestView.render();
4271         document.body.appendChild(this._input);
4272 
4273         return this;
4274 
4275     }
4276 
4277     initUI(target, data){
4278         this.imageViewer.domElement.style.display = 
4279         this.colorTestView.domElement.style.display = 
4280         this._input.style.display = "none";
4281 
4282         for(let i = 0, c = this._menuViews.length; i < c; i++) this._menuViews[i].exit();
4283 
4284         this.cae.disposeEvent();
4285         this.cae._eventList = {};
4286         this.#isLine = false;
4287         this.#h = this.#i = 
4288         this._menuViews.length = 
4289         this.list.length = 
4290         //this._lines.length = 
4291         this._loopData.length = 
4292         this._loopObject.length = 0;
4293         
4294         if(target && this.target !== target) this.target = target;
4295         if(data && this.data !== data) this.data = data;
4296         if(!this.target || !this.data) return this;
4297         this.updateCanvas();
4298 
4299         //create view
4300         for(let k = 0, len = this.data.length, data, y; k < len; k++){
4301             data = this.data[k]; 
4302             y = this._height * this.#i + this.#i * this._margin;
4303             if(this._initUI(data, y) !== true) continue;
4304             this.#h += this._height;
4305             this.#i++;
4306             this.#isLine = data.type === 'line';
4307             //nextMake = this._initMake(data, y, nextMake);
4308 
4309             /* //this._lines
4310             c = this._lines.length;
4311             for(i = 0; i < c; i++){
4312                 ca = this._lines[i];
4313                 k = ca._content.indexOf(data.valueUrl);
4314                 if(k !== -1) ca._content[k+1] = true;
4315             } */
4316 
4317         }
4318         
4319         /* //this._lines 是否绘制
4320         for(let i = 0, c = this._lines.length, k, len, ca = null; i < c; i++){
4321             ca = this._lines[i];
4322 
4323             len = ca._content.length;
4324             for(k = 0; k < len; k += 2){
4325                 if(ca._content[k+1] === true){
4326                     ca = null;
4327                     break;
4328                 }
4329             }
4330             
4331             if(ca !== null){
4332                 i = this.list.indexOf(ca);
4333                 this.list.splice(i, 1);
4334                 len = this.list.length;
4335                 for(k = i; k < len; k++) this.list[k].box.y -= this._height;
4336             }
4337 
4338         } */
4339 
4340         //.domElement size
4341         var _h = this.#h + this.#i * this._margin;
4342         this.initList().size(this.clientWidth, _h < this.clientHeight ? this.clientHeight - this._margin*2 : _h).draw();
4343 
4344         //._globalCA event
4345         this._globalCA.box.set(0, 0, this.width, this.height);
4346         this.cae.add(this._globalCA, "down", ()=>{
4347             this._hiddenImageViewer();
4348             this._hiddenColorTestView();
4349         });
4350         this.cae.add(this._globalCA, "up", CanvasAnimateUI.stopTimers);
4351         
4352         //this.autoHeight
4353         if(this.autoHeight === true) this.parent.style.height = `${this.box.h+this._margin*2}px`;
4354 
4355         return this;
4356     }
4357 
4358     update(redraw){
4359         if(this.parent.offsetWidth === 0) return;
4360 
4361         const c = this._loopObject.length;
4362         for(let i = 0, v; i < c; i++){
4363             v = this._loopObject[i];
4364             if(typeof v.update === "function") v.update();
4365             else if(Array.isArray(v.update) === true){
4366                 for(let k = 0, len = v.update.length; k < len; k++) v.update[k]();
4367             }
4368         }
4369 
4370         if(redraw !== false) this.redraw();
4371 
4372     }
4373 
4374     style(style = ''){
4375         style += `
4376             width: ${this.clientWidth}px;
4377             height: ${this.clientHeight}px;
4378             padding: ${this._margin}px;
4379             overflow: hidden auto;
4380         `;
4381 
4382         this.parent.style = style;
4383         super.updateCanvas();
4384         
4385         return this;
4386     }
4387 
4388     pos(left, top){
4389         this.parent.style.left = left+"px";
4390         this.parent.style.top = top+"px";
4391         super.updateCanvas();
4392         return this;
4393     }
4394 
4395     getView(data){
4396         const i = this._loopData.indexOf(data);
4397         if(i !== -1) return this._loopObject[i];
4398         
4399     }
4400 
4401     getData(valueUrl){
4402         const len = this.data.length;
4403         for(let k = 0; k < len; k++){
4404             if(this.data[k].valueUrl === valueUrl) return this.data[k];
4405         }
4406 
4407     }
4408 
4409 
4410     //以下方法限内部使用
4411     _redrawTarget(y){ //只重绘目标(仅一列)
4412         CanvasAnimateUI.emptyCAC.box.set(0, y, this.width, this._height);
4413         this.drawTarget(CanvasAnimateUI.emptyCAC);
4414 
4415     }
4416 
4417     _bindHover(cai, y = 0, title = "", out = 0, over = 1){
4418         this.cae.add(cai, "out", ()=>{
4419             if(title !== "") this.domElement.title = "";
4420             this.domElement.style.cursor = "";
4421             cai.set(out);
4422             this._redrawTarget(y);
4423         });
4424 
4425         this.cae.add(cai, "over", ()=>{
4426             if(title !== "") this.domElement.title = title;
4427             this.domElement.style.cursor = "pointer";
4428             cai.set(over);
4429             this._redrawTarget(y);
4430         });
4431 
4432         cai.set(out);
4433         return cai;
4434     }
4435 
4436     _initMake(data, y, nextMake){
4437 
4438         if(typeof data.make === "object"){
4439             if(nextMake === false) this.list.push(new CanvasAnimate().setImage(this.img_line).pos(0, y - this.img_line.height/2));
4440 
4441             var r = false;
4442             if(Array.isArray(data.make) === true){
4443                 const len = data.make.length;
4444                 for(let k = 0; k < len; k++){
4445                     y = this._height * this.#i + this.#i * this._margin;
4446                     if(this._initUI(data.make[k], y) !== true) continue;
4447                     if(r !== true) r = true;
4448                     this.#h += this._height;
4449                     this.#i++;
4450                     
4451                 }
4452                 
4453             }
4454 
4455             else{
4456                 y = this._height * this.#i + this.#i * this._margin;
4457                 r = this._initUI(data.make, y);
4458                 if(r === true){
4459                     this.#h += this._height;
4460                     this.#i++;
4461                 }
4462             }
4463 
4464             if(r === true) this.list.push(new CanvasAnimate().setImage(this.img_line).pos(0, y + this._height - this.img_line.height/2));
4465 
4466             return r;
4467         }
4468 
4469         return false;
4470     }
4471 
4472     _initImages(numberCursorSize = 0.5){
4473         if(numberCursorSize > 1) numberCursorSize = 1;
4474 
4475         const cac =  CanvasAnimateUI.emptyCAC, mw = this._titleWidth + this._conWidth, f_2 = this._fontSIze / 2, 
4476         m_2 = this._margin/2, m2 = this._margin*2, _h8 = this._height*0.8, mar = (this._height -  this._fontSIze) / 2,
4477         gradient_line = cac.linearGradient(0, this._height, mw, this._height);
4478 
4479         //禁用背景图片
4480         this.img_dis = cac.size(mw, this._height).rect().fill("rgba(0,0,0,0.4)").shear();
4481 
4482         //解释说明图片
4483         this.img_explainA = cac.size(f_2 + m_2 * 2, f_2 + m_2 * 2).text("?", "#000", f_2 + m_2, "center", "center").computeCircle().arc(cac.circle).stroke("#000").shear();
4484         this.img_explainB = cac.clear().text("?", "#0000ff", f_2 + m_2, "center", "center").stroke("#0000ff").shear();
4485 
4486         //make 分割线图片
4487         cac.gradientColorSymme(gradient_line, ["rgba(0,0,0,0)", "rgba(0,0,0,0.5)"]);
4488         this.img_line = cac.size(mw, this._height).line(1, this._height/2, mw - 1, this._height/2).stroke(gradient_line, 1).shear();
4489 
4490         //冒号图片
4491         this.img_colon = cac.size(m2, this._height).rect().text(":", "", this._fontSIze, "center", "center").shear();
4492 
4493         //复选框图片
4494         this.img_checkboxA = cac.size(this._fontSIze + this._margin, this._fontSIze + this._margin).rect().fill("#fff").stroke(this._conColor, 1).shear();
4495         this.img_checkboxB = cac.text("✔", "#0000ff", this._fontSIze, "center", "center").shear();
4496         this.img_checkboxC = cac.clear().stroke("#0000ff", 1).shear();
4497 
4498         //颜色控件图片
4499         this.img_colorBG = cac.size(this._height, this._height).rect().drawTransparentBG().shear();
4500         this.img_colorA = cac.clear().stroke(this._conColor).shear();
4501         this.img_colorB = cac.clear().stroke("#0000ff").shear();
4502 
4503         //按钮图片
4504         this.img_buttonEditA = cac.clear().text("▤", "#000000", _h8, "center", "center").shear();
4505         this.img_buttonEditB = cac.clear().text("▤", "#0000ff", _h8, "center", "center").shear();
4506 
4507         cac.rotate(Math.PI/2);
4508         this.img_buttonEditC = cac.clear().text(">", "#000000", _h8, "center", "center").shear();
4509         this.img_buttonEditD = cac.clear().text(">", "#0000ff", _h8, "center", "center").shear();
4510         cac.unRotate(true);
4511 
4512         this.img_buttonResetA = cac.size(this._height, this._height).text("↻", "#000000", this._fontSIze, "center", "center").shear();
4513         this.img_buttonResetB = cac.clear().text("↻", "#0000ff", this._fontSIze, "center", "center").shear();
4514 
4515         cac.box.w = 0;
4516         cac.text("Button", "#000", this._fontSIze, "center", "center");
4517         cac.size(cac.box.w+m2, this._fontSIze+m2).rect().shadow("", 1, 1, 1);
4518         this.img_buttonA = cac.clear().shadow("#666").fill("#eee").shadow().text("Button", "#000", this._fontSIze, "center", "center").shear();
4519         this.img_buttonB = cac.clear().shadow("#666").fill("#fff").shadow().text("Button", "#0000ff", this._fontSIze, "center", "center").shear();
4520         this.img_buttonC = cac.clear().shadow("#666", 0, -1, -1).fill("#fff").shadow().text("Button", "#0000ff", this._fontSIze, "center", "center").shear();
4521 
4522         //数字控件图片
4523         this.img_numButSubA = cac.size(f_2, f_2).path([f_2 / 2, 0, f_2, f_2, 0, f_2], true).fill("#000").shear();
4524         this.img_numButSubB = cac.clear().fill("blue").shear();
4525 
4526         this.img_numButAddA = cac.clear().path([f_2 / 2, f_2, f_2, 0, 0, 0], true).fill("#000").shear();
4527         this.img_numButAddB = cac.clear().fill("blue").shear();
4528 
4529         cac.box.w = 0;
4530         this.img_numButSignA = cac.size(mar*numberCursorSize, mar*numberCursorSize).computeCircle().arc(cac.circle).fill("#000").shear();
4531         this.img_numButSignB = cac.clear().arc(cac.circle).fill("blue").shear();
4532 
4533         this._numberInputWidth = (this._conWidth - m2 - this._height) / 3 - f_2;
4534         this.img_numBoxA = cac.size(this._numberInputWidth, this._fontSIze+2).rect().shadow("#666", 1, 1, 1).stroke("#eee").shear();
4535         this.img_numBoxB = cac.clear().shadow().stroke("blue", 1).shear();
4536 
4537         cac.size(this._height, this._fontSIze).shadow("#666", 1, 1, 1).rect();
4538         this.img_buttonStepA = cac.stroke("#eee", 1).shear();
4539         this.img_buttonStepB = cac.clear().shadow().stroke("blue", 1).shear();
4540 
4541         //进度条图片
4542         this.img_progress = cac.size(this._conWidth - m2 - this._height, 2).line(0, 1, this._conWidth, 1).stroke("blue", 2).shear();
4543 
4544     }
4545 
4546     _isDrawByData(data){
4547         const v = this._getValueByUrl(data.valueUrl);
4548 
4549         if(v === undefined) return;
4550         
4551         switch(data.type){
4552             case "image":
4553             return true;
4554 
4555             case "select":
4556             return true;
4557 
4558             case "json":
4559             return true;
4560         }
4561 
4562         if(Array.isArray(data.selectList) === true) return true;
4563         
4564         const ty = typeof v;
4565         switch(ty){
4566             case "object":
4567             if(v === null || (v.isVector2 !== true && v.isVector3 !== true && v.isEuler !== true && v.isColor !== true && v.isRGBColor !== true && this.isCanvasImage(v) !== true)) return;
4568             break;
4569 
4570             case "string":
4571             break;
4572 
4573             case "number":
4574             break;
4575 
4576             case "boolean":
4577             break;
4578             
4579             case "function":
4580             break;
4581 
4582             default: return;
4583         }
4584 
4585         return true;
4586     }
4587 
4588     _initUI(data, y){
4589 
4590         if(data.type === 'line'){
4591             if(this.#isLine === true) return;
4592             return this.createLine(data, y);
4593         }
4594 
4595         const v = this._getValueByUrl(data.valueUrl);
4596 
4597         if(v === undefined) return;
4598         
4599         switch(data.type){
4600             case "image":
4601             this.createFileImage(data, y);
4602             return true;
4603 
4604             case "select":
4605             this.createSelect(data, v, y);
4606             return true;
4607 
4608             case "json":
4609             this.createFileJSON(data, y);
4610             return;
4611 
4612         }
4613 
4614         if(Array.isArray(data.selectList) === true){
4615             this.createSelect(data, v, y);
4616             return true;
4617         }
4618         
4619         const ty = typeof v;
4620     
4621         switch(ty){
4622             case "object":
4623             if(v === null) return;
4624             if(v.isVector2 === true || v.isPoint === true) this.createNumbers(data, "Vector2", y);
4625             else if(v.isVector3 === true) this.createNumbers(data, "Vector3", y);
4626             else if(v.isEuler === true) this.createNumbers(data, "Euler", y);
4627             else if(v.isColor === true || v.isRGBColor === true) this.createColor(data, v, y);
4628             else if(this.isCanvasImage(v) === true) this.createFileImage(data, y);
4629             else return;
4630             break;
4631 
4632             case "string":
4633             const _v = this.colorTestView.getColor(v);
4634             if(_v !== "") this.createColor(data, _v, y);
4635             else this.createText(data, v, y);
4636             break;
4637 
4638             case "number":
4639             this.createNumbers(data, "number", y);
4640             break;
4641 
4642             case "boolean":
4643             this.createCheckbox(data, v, y);
4644             break;
4645             
4646             case "function":
4647             this.createFunc(data, y);
4648             break;
4649 
4650             default: return;
4651         }
4652 
4653         return true;
4654     }
4655 
4656     _string(str){
4657         if(str.length > this._stringMaxCount) return str.substr(0, this._stringMaxCount);
4658         return str;
4659     }
4660 
4661     _number(num, range){
4662         if(num < range.min) num = range.min;
4663         else if(num > range.max) num = range.max;
4664         
4665         return num;
4666     }
4667 
4668     _numToStr(num){
4669         return this._string(String(Math.floor(num * 1000) / 1000));
4670     }
4671 
4672     _getValueByUrl(valueUrl){
4673         try{
4674 
4675             return eval("this.target" + valueUrl);
4676             
4677         }
4678 
4679         catch(e){
4680 
4681             return;
4682 
4683         }
4684 
4685     }
4686 
4687     _getEventParam(data, y, target, disableCA){
4688         disableCA.visible = typeof data.disable === "boolean" ? data.disable : false;
4689         this.cae.add(disableCA, "click", CanvasAnimateUI.emptyFunc);
4690         this.cae.add(disableCA, "down", CanvasAnimateUI.emptyFunc);
4691         
4692         const result = {
4693             target: target,
4694             data: data,
4695             scope: this,
4696             redraw: ()=>this._redrawTarget(y),
4697             get disable(){
4698                 return disableCA.visible;
4699             },
4700             set disable(v){
4701                 if(typeof v === "boolean") disableCA.visible = v;
4702 
4703             },
4704 
4705         }
4706 
4707         if(data.update === true || (this.globalUpdate === true && data.update !== false)){
4708             this._loopData.push(data);
4709             this._loopObject.push(result);
4710         }
4711 
4712         return result;
4713     }
4714 
4715     _styleShadowConvex(elem){
4716         elem.style.boxShadow = '2px 2px 4px 0px #666666';
4717         return elem;
4718     }
4719 
4720     _getParent(){
4721         const elem = this._styleShadowConvex(document.createElement("div"));//全局 parent
4722         
4723         elem.style = `
4724             width: ${this.clientWidth}px;
4725             height: ${this.clientHeight}px;
4726             padding: ${this._margin}px;
4727             overflow: hidden auto;
4728         `;
4729         
4730         elem.appendChild(this.domElement);
4731         elem.onscroll = () => this.updateCanvas();
4732 
4733         return elem;
4734     }
4735 
4736     _getInput(){
4737         const elem = document.createElement("textarea");
4738         elem.style = `
4739             width: ${this._conWidth*0.9}px;
4740             height: ${this._conWidth*0.45}px;
4741             position: absolute;
4742             left: 0px;
4743             top: 0px;
4744             display: none;
4745             z-index: 99999;
4746             font-size:${this._fontSIze}px;
4747             padding: 2px;
4748         `;
4749 
4750         elem.onchange = e => this._hiddenInput(e.target.value);
4751         elem.onblur = () => elem.style.display = "none";
4752         //this.parent.appendChild(elem);
4753         
4754         return elem;
4755     }
4756 
4757     _getSelectIndex(list, value){
4758         for(let k = 0, len = list.length; k < len; k++){
4759             if(list[k].value === value) return k;
4760         }
4761         return -1;
4762     }
4763 
4764     _getValueUrl(valueUrl){
4765         var urls = [], str = "", tar = this.target;
4766 
4767         for(let k = 0, v, len = valueUrl.length; k < len; k++){
4768             v = valueUrl[k];
4769             if(v === " ") continue;
4770             
4771             if(v === '.' || v === '[' || v === ']'){
4772                 if(str !== ""){
4773                     urls.push(str);
4774                     str = "";
4775                 }
4776                 
4777             }
4778 
4779             else str += v;
4780 
4781         }
4782 
4783         if(str !== "") urls.push(str);
4784         
4785         if(urls.length > 1){
4786             let _len = urls.length - 1;
4787             for(let k = 0; k < _len; k++) tar = tar[urls[k]];
4788             str = urls[_len];
4789         }
4790 
4791         else str = urls[0];
4792         
4793         urls = undefined;
4794         
4795         return {
4796             object: tar,
4797             name: str,
4798             get value(){
4799                 return this.object[this.name];
4800             },
4801             set value(v){
4802                 this.object[this.name] = v;
4803             },
4804 
4805         }
4806         
4807     }
4808 
4809     _showInput(y, func, value){
4810         this._input.style.display = "block";
4811         const rect = this._input.getBoundingClientRect();
4812         
4813         y += this._height+this.domElementRect.y;
4814         if(y + rect.height > window.innerHeight) y -= this._height + rect.height;
4815         this._input.style.top = y + "px";
4816         
4817         var x = this.domElementRect.x + this._titleWidth + this._conWidth - rect.width;
4818         if(x + rect.width > window.innerWidth) x -= x + rect.width - window.innerWidth + this._margin;
4819         this._input.style.left = x + "px";
4820 
4821         this._inputFunc = func;
4822         this._input.value = value;
4823         this._input.select();
4824 
4825     }
4826 
4827     _showColorTestView(y, func, value){
4828         this.colorTestView.onchange = func;
4829         this.colorTestView.setValueString(value).update(true);
4830         this.colorTestView.domElement.style.display = "block";
4831 
4832         y += this.domElementRect.y - this.colorTestView.box.h;
4833         if(y < 0) y += this.colorTestView.box.h + this._height;
4834 
4835         var x = this.domElementRect.x + this._titleWidth + this._conWidth - this.colorTestView.box.w;
4836         if(x + this.colorTestView.box.w > window.innerWidth) x -= x + this.colorTestView.box.w - window.innerWidth + this._margin;
4837 
4838         this.colorTestView.pos(x, y);
4839         
4840     }
4841 
4842     _showImageViewer(y, image){
4843         this.imageViewer.setImage(image)
4844         .setViewportScale()
4845         .center()
4846         .drawImage()
4847         .redraw();
4848         this.imageViewer.domElement.style.display = "block";
4849 
4850         y += this.domElementRect.y - this.imageViewer.box.h;
4851         if(y < 0) y += this.imageViewer.box.h + this._height;
4852 
4853         var x = this.domElementRect.x;
4854         if(x + this.imageViewer.box.w > window.innerWidth) x -= x + this.imageViewer.box.w - window.innerWidth + this._margin;
4855         
4856         this.imageViewer.pos(x, y);
4857 
4858     }
4859 
4860     _hiddenInput(value){
4861         this._input.style.display = "none";
4862         if(this._inputFunc !== null){
4863             this._inputFunc(value);
4864             
4865         }
4866     }
4867 
4868     _hiddenColorTestView(){
4869         if(this.colorTestView.onchange !== null){
4870             this.colorTestView.onchange = null;
4871             this.colorTestView.domElement.style.display = "none";
4872         }
4873     }
4874 
4875     _hiddenImageViewer(){
4876         this.imageViewer.setImage().clear();
4877         this.imageViewer.domElement.style.display = "none";
4878 
4879     }
4880 
4881     _onChanges(data, param){
4882         if(typeof data.onChange === "function") data.onChange(param);
4883         //else{
4884         //    if(type === "func") param.target.value.call(param.target.object, param); //eval('this.target'+data.valueUrl+'(param)'); //param.target.value(param);
4885 
4886         //}
4887 
4888         if(this.onChanges !== null) this.onChanges(param);
4889         
4890     }
4891 
4892     _createMenuView(list){
4893         MenuView.reset();
4894         const menuView = new MenuView(list);
4895         this._styleShadowConvex(menuView.view.domElement);
4896         menuView.view.domElement.style.zIndex = "99999";
4897         this._menuViews.push(menuView);
4898         return menuView;
4899     }
4900 
4901     _cretaeTitle(textTitle, textExplain, y){ //标题
4902         if(typeof textTitle === "string"){
4903             //data.title
4904             const title = new CanvasAnimateCustom().size(this._titleWidth, this._height).text(textTitle, this._titleColor, this._fontSIze, 0, "center").pos(0, y);
4905             this.cae.add(title, "out", this._funcTitleOut);
4906             this.cae.add(title, "over", ()=>this.domElement.title = textTitle);
4907             this.list.push(title);
4908 
4909             //data.explain
4910             if(typeof textExplain === "string"){
4911                 this.list.push(this._bindHover(new CanvasAnimateImages([this.img_explainA, this.img_explainB]), y, textExplain).pos(title.box.maxX() - this.img_explainA.width - 1, title.box.y + 1));
4912                 
4913             }
4914 
4915             return title.box.maxX();
4916         }
4917         
4918         return this._titleWidth;
4919     }
4920 
4921     _createNumber(data, progress, param, x, y, target, name){
4922         var v;
4923         const bor = this._bindHover(new CanvasAnimateImages([this.img_numBoxA, this.img_numBoxB]), y).pos(x, y + (this._height - this.img_numBoxA.height)/2),
4924         con = new CanvasAnimateCustom().size(this._numberInputWidth, this._height).pos(x, y),
4925         but_sub = this._bindHover(new CanvasAnimateImages([this.img_numButSubA, this.img_numButSubB]), y).pos(bor.box.maxX(), y + (this._height - this.img_numButSubA.height * 2) / 2 - 1),
4926         but_add = this._bindHover(new CanvasAnimateImages([this.img_numButAddA, this.img_numButAddB]), y).pos(but_sub.box.x, but_sub.box.maxY() + 1),
4927         but_sign = new CanvasAnimateImages([this.img_numButSignA, this.img_numButSignB]).pos(this._numberInputWidth / 2 + bor.box.x - this.img_numButSignA.width/2, bor.box.y - this.img_numButSignA.height),
4928         
4929         setValue = (value, isUpdate) => {
4930             value = this._number(value, param);
4931             if(v !== value){
4932                 target[name] = v = value;
4933                 param.valueName = name;
4934                 con.clear().text(this._numToStr(v), this._conColor, this._fontSIze, 2, "center");
4935                 if(but_sign.i === 1) progress._updateCursor(v);
4936 
4937                 if(isUpdate !== false){
4938                     this._onChanges(data, param);
4939                     param.redraw();
4940                 }
4941                 
4942                 return true;
4943             }
4944 
4945         },
4946 
4947         toValue = ()=>{
4948             if(v !== target[name]){
4949                 v = this._number(target[name], param);
4950                 con.clear().text(this._numToStr(v), this._conColor, this._fontSIze, 2, "center");
4951                 if(typeof progress._updateCursor === "function") progress._updateCursor(v);
4952             }
4953             
4954         },
4955         
4956         onInputHidden = value => {
4957             value = parseFloat(value);
4958             if(isNaN(value) === true) return;
4959             setValue(value);
4960             
4961         }
4962         
4963         //event
4964         this.cae.add(bor, "click", () => this._showInput(y, onInputHidden, v));
4965         this.cae.add(bor, "out", this._funcTitleOut);
4966         this.cae.add(bor, "over", ()=>this.domElement.title = v);
4967         this.cae.add(but_sign, "click", ()=>{
4968             but_sign.next();
4969             if(but_sign.i === 0) progress._updateCursorAtSign();
4970             else progress._updateCursor(v);
4971             param.redraw();
4972             
4973         });
4974         //this.cae.add(but_sign, "over", ()=>{
4975         //    progress._updateCursor(v);
4976         //    param.redraw();
4977         //});
4978 
4979         this.cae.add(but_sub, "up", CanvasAnimateUI.stopTimers);
4980         this.cae.add(but_add, "up", CanvasAnimateUI.stopTimers);
4981         const _setValueSub = ()=>{
4982             CanvasAnimateUI.emptyTimerA.speed = 150;
4983             setValue(v - (param.type !== 'Euler' ? param.step : param.__step));
4984         },
4985         _setValueAdd = ()=>{
4986             CanvasAnimateUI.emptyTimerA.speed = 150;
4987             setValue(v + (param.type !== 'Euler' ? param.step : param.__step));
4988         }
4989         this.cae.add(but_sub, "down", ()=>{
4990             _setValueSub();
4991             CanvasAnimateUI.emptyTimerA.start(_setValueSub, 500);
4992             
4993         });
4994         this.cae.add(but_add, "down", ()=>{
4995             _setValueAdd();
4996             CanvasAnimateUI.emptyTimerA.start(_setValueAdd, 500);
4997 
4998         });
4999 
5000         this.cae.add(but_sub, "out", this._funcTitleOut);
5001         this.cae.add(but_sub, "over", () => this.domElement.title = "min: "+param.min);
5002 
5003         this.cae.add(but_add, "out", this._funcTitleOut);
5004         this.cae.add(but_add, "over", () => this.domElement.title = "max: "+param.max);
5005 
5006         if(Array.isArray(param.update) === false) param.update = [];
5007         param.update.push(toValue);
5008 
5009         but_sign.set(0);
5010         this.list.push(con, bor, but_sub, but_add, but_sign);
5011 
5012         return {
5013             maxX: but_sub.box.maxX(),
5014             sign: but_sign,
5015             setValue: setValue,
5016             toValue: toValue,
5017         }
5018     }
5019 
5020     createLine(data, y){
5021         var is;
5022 
5023         if(typeof data.value === "string") is = this._getValueByUrl(data.value) !== undefined;
5024 
5025         else if(Array.isArray(data.value) === true){
5026             for(let k = 0, len = data.value.length; k < len; k++){
5027                 if(this._getValueByUrl(data.value[k]) !== undefined){
5028                     is = true;
5029                     break;
5030                 }
5031             }
5032         }
5033 
5034         if(is === true) this.list.push(new CanvasAnimate(this.img_line).pos(0, y));
5035 
5036         return is;
5037     }
5038 
5039     createText(data, v, y){
5040         const target = this._getValueUrl(data.valueUrl),
5041         titleWidth = this._cretaeTitle(data.title, data.explain, y),
5042 
5043         colon = new CanvasAnimate(this.img_colon).pos(titleWidth, y),
5044         con = new CanvasAnimateCustom().size(this._conWidth - colon.box.w - this._height, this._height).text(this._string(v), this._conColor, this._fontSIze, 0, "center").pos(colon.box.maxX(), y),
5045         but = this._bindHover(new CanvasAnimateImages([this.img_buttonEditA, this.img_buttonEditB]), y).pos(con.box.maxX(), y),
5046         disableCA = new CanvasAnimate(this.img_dis).pos(0, y),
5047 
5048         eventParam = this._getEventParam(data, y, target, disableCA),
5049         onInputHaidden = value => {
5050             target.value = v = value;
5051             con.clear().text(this._string(v), this._conColor, this._fontSIze, 0, "center");
5052             this._onChanges(data, eventParam);
5053             this._redrawTarget(y);
5054         }
5055 
5056         //event
5057         this.cae.add(con, "out", this._funcTitleOut);
5058         this.cae.add(con, "over", ()=>this.domElement.title = v);
5059         this.cae.add(but, "click", () => this._showInput(y, onInputHaidden, v));
5060 
5061         eventParam.update = ()=>{
5062             if(target.value !== v){
5063                 v = target.value;
5064                 con.clear().text(this._string(v), this._conColor, this._fontSIze, 0, "center");
5065             }
5066             
5067         }
5068         
5069         this.list.push(colon, con, but, disableCA);
5070         
5071     }
5072 
5073     createColor(data, v, y){
5074         const isObj = typeof v === "object";
5075         if(isObj === true) v = v.getStyle(); //兼容 class Color
5076         
5077         const target = this._getValueUrl(data.valueUrl),
5078         titleWidth = this._cretaeTitle(data.title, data.explain, y),
5079         colon = new CanvasAnimate(this.img_colon).pos(titleWidth, y),
5080         conA = new CanvasAnimateCustom().size(this._conWidth - colon.box.w - this._height * 2, this._height).text(v, this._conColor, this._fontSIze, 0, "center").pos(colon.box.maxX(), y),
5081         conBG = new CanvasAnimate(this.img_colorBG).pos(conA.box.maxX(), y),
5082         conB = new CanvasAnimateCustom().size(this._height, this._height).rect().shadow("#666", 2).fill(v).pos(conBG.box.x, y),
5083         conC = this._bindHover(new CanvasAnimateImages([this.img_colorA, this.img_colorB]), y).pos(conBG.box.x, y),
5084         but = this._bindHover(new CanvasAnimateImages([this.img_buttonEditA, this.img_buttonEditB]), y).pos(conB.box.maxX(), y), 
5085         disableCA = new CanvasAnimate(this.img_dis).pos(0, y),
5086         eventParam = this._getEventParam(data, y, target, disableCA),
5087 
5088         setValue = value => {
5089             value = this.colorTestView.getColor(value);
5090             
5091             if(value === "") return;
5092             v = value;
5093             if(isObj === false) target.value = v;
5094             else target.value.set(v);
5095             
5096             conA.clear().text(v, this._conColor, this._fontSIze, 0, "center");
5097             conB.clear().fill(v);
5098             this._onChanges(data, eventParam);
5099             this._redrawTarget(y);
5100         }
5101 
5102         //event
5103         this.cae.add(but, "click", () => this._showInput(y, setValue, v));
5104         this.cae.add(conC, "click", () => this._showColorTestView(y, setValue, v));
5105         
5106         this.cae.add(conA, "out", this._funcTitleOut);
5107         this.cae.add(conA, "over", ()=>this.domElement.title = v);
5108         
5109         eventParam.update = ()=>{
5110             if(target.value !== v){
5111                 v = isObj === false ? target.value : target.value.getStyle();
5112                 conA.clear().text(v, this._conColor, this._fontSIze, 0, "center");
5113                 conB.clear().fill(v);
5114             }
5115             
5116         }
5117 
5118         this.list.push(colon, conA, conBG, conB, conC, but, disableCA);
5119     }
5120 
5121     createCheckbox(data, v, y){
5122         const target = this._getValueUrl(data.valueUrl),
5123         titleWidth = this._cretaeTitle(data.title, data.explain, y),
5124         colon = new CanvasAnimate(this.img_colon).pos(titleWidth, y),
5125         con = new CanvasAnimateImages([this.img_checkboxA, this.img_checkboxB]).pos(colon.box.maxX(), y + (this._height - this.img_checkboxA.height) / 2),
5126         con_hover = new CanvasAnimate(this.img_checkboxC).pos(con.box.x, con.box.y),
5127         disableCA = new CanvasAnimate(this.img_dis).pos(0, y),
5128         eventParam = this._getEventParam(data, y, target, disableCA);
5129         con.set(v === true ? 1 : 0);
5130 
5131         //event
5132         con_hover.visible = false;
5133         this.cae.add(con, "out", ()=>{
5134             this._funcTitleOut();
5135             con_hover.visible = false;
5136             this._redrawTarget(y);
5137         });
5138 
5139         this.cae.add(con, "over", ()=>{
5140             this.domElement.title = String(v);
5141             con_hover.visible = true;
5142             this._redrawTarget(y);
5143         });
5144 
5145         this.cae.add(con, "click", () => {
5146             con.next();
5147             v = con.i === 1 ? true : false;
5148             target.value = v;
5149             this._onChanges(data, eventParam);
5150             this._redrawTarget(y);
5151         });
5152 
5153         //update
5154         eventParam.update = ()=>{
5155             if(target.value !== v){
5156                 v = target.value;
5157                 con.set(v === true ? 1 : 0);
5158             }
5159             
5160         }
5161         
5162         this.list.push(colon, con, con_hover, disableCA);
5163 
5164     }
5165 
5166     createFunc(data, y){
5167         const titleWidth = this._cretaeTitle(data.title, data.explain, y),
5168         colon = new CanvasAnimate(this.img_colon).pos(titleWidth, y),
5169         con = new CanvasAnimate(this.img_buttonA).pos(colon.box.maxX(), y + (this._height - this.img_buttonA.height) / 2),
5170         con_hover = new CanvasAnimate(this.img_buttonB).pos(con.box.x, con.box.y),
5171         con_click = new CanvasAnimate(this.img_buttonC).pos(con.box.x, con.box.y),
5172         disableCA = new CanvasAnimate(this.img_dis).pos(0, y),
5173 
5174         target = this._getValueUrl(data.valueUrl),
5175         param = this._getEventParam(data, y, target, disableCA);
5176 
5177         con_click.visible = false;
5178         con_hover.visible = false;
5179 
5180         //event
5181         this.cae.add(con, "out", ()=>{
5182             this._funcTitleOut();
5183             con_hover.visible = false;
5184             this._redrawTarget(y);
5185         });
5186 
5187         this.cae.add(con, "over", ()=>{
5188             this.domElement.title = target.value.name;
5189             con_hover.visible = true;
5190             this._redrawTarget(y);
5191         });
5192 
5193         this.cae.add(con, "down", () => {
5194             con_hover.visible = false;
5195             con_click.visible = true;
5196             this._redrawTarget(y);
5197         });
5198 
5199         this.cae.add(con, "up", () => {
5200             con_click.visible = false;
5201             //this._onChanges(data, param, "func"); //执行回调1
5202             //param.target.value.call(param.target.object, param);  //执行回调2
5203             //eval('this.target'+data.valueUrl+'(param)'); //执行回调3
5204             //param.target.value(param);
5205             if(this.target){
5206                 if(!data.eventFunc){
5207                     eval('this.target'+data.valueUrl+'(param)');
5208                     if(this.onChanges !== null) this.onChanges(param);
5209                 }
5210                 else this._onChanges(data, param);
5211             }
5212             this._redrawTarget(y);
5213         });
5214 
5215         param.update = CanvasAnimateUI.emptyFunc;
5216 
5217         this.list.push(colon, con, con_hover, con_click, disableCA);
5218         
5219     }
5220 
5221     createSelect(data, v, y){
5222         var k = this._getSelectIndex(data.selectList, v), menuParam = null;
5223 
5224         const target = this._getValueUrl(data.valueUrl), _list = [], list = data.selectList, 
5225         titleWidth = this._cretaeTitle(data.title, data.explain, y),
5226         colon = new CanvasAnimate(this.img_colon).pos(titleWidth, y),
5227         con = new CanvasAnimateCustom().size(this._conWidth - colon.box.w - this._height, this._height).text(k !== -1 ? this._string(list[k].name) : "undefined", this._conColor, this._fontSIze, 0, "center").pos(colon.box.maxX(), y),
5228         but = this._bindHover(new CanvasAnimateImages([this.img_buttonEditC, this.img_buttonEditD]), y).pos(con.box.maxX(), y),
5229         disableCA = new CanvasAnimate(this.img_dis).pos(0, y),
5230         eventParam = this._getEventParam(data, y, target, disableCA),
5231 
5232         func = value => {
5233             menuParam.lcon_def.set();
5234             menuParam.background.set();
5235             menuParam = value;
5236             menuParam.lcon_def.set(1);
5237             menuParam.background.set(2);
5238 
5239             v = list[value.key].value;
5240             target.value = v;
5241             con.clear().text(this._string(list[value.key].name), this._conColor, this._fontSIze, 0, "center");
5242             this._onChanges(data, eventParam);
5243             this._redrawTarget(y);
5244             value.target.visible = false;
5245             
5246         }
5247 
5248         for(let i = 0, len = list.length; i < len; i++) _list.push({name: list[i].name, func: func});
5249         this.cae.add(con, "out", this._funcTitleOut);
5250         this.cae.add(con, "over", ()=>this.domElement.title = v);
5251         
5252         eventParam.update = ()=>{
5253             if(target.value !== v){
5254                 v = target.value;
5255                 k = this._getSelectIndex(list, v);
5256                 con.clear().text(k !== -1 ? this._string(list[k].name) : "undefined", this._conColor, this._fontSIze, 0, "center");
5257             }
5258             
5259         }
5260 
5261         //init menuView
5262         var menuView = null;
5263         this.cae.add(this._globalCA, "down", () => {
5264             if(menuView !== null && menuView.visible === true) menuView.visible = false;
5265         });
5266 
5267         this.cae.add(but, "click", () => {
5268 
5269             //这里减轻一下.initUI()的压力: 既在初始化时先不创建 menuView 控件;
5270             if(menuView === null){
5271                 menuView = this._createMenuView(_list)
5272                 if(k !== -1){
5273                     menuParam = menuView.views[k];
5274                     menuParam.lcon_def.set(1);
5275                     menuParam.background.set(2);
5276                     
5277                 }
5278             }
5279 
5280             let top = y + this._height + this.domElementRect.y;
5281             if(top + menuView.height > window.innerHeight) top -= this._height + menuView.height;
5282 
5283             let left = this.domElementRect.x + this._titleWidth + this._conWidth - menuView.width;
5284             if(left + menuView.width > window.innerWidth) left -= left + menuView.width - window.innerWidth + this._margin;
5285 
5286             menuView.view.pos(left, top);
5287             menuView.visible = true;
5288             
5289         });
5290 
5291 
5292         this.list.push(colon, con, but, disableCA);
5293     }
5294 
5295     createNumber(data, y, param, progress, progressCursor){
5296 
5297         //param 特有属性 (min, max, step)
5298         param.min = (typeof data.range === "object" && typeof data.range.min === "number") ? data.range.min : this.defiendRange.min;
5299         param.max = (typeof data.range === "object" && typeof data.range.max === "number") ? data.range.max : this.defiendRange.max;
5300         var _step = (typeof data.range === "object" && typeof data.range.step === "number") ? data.range.step : this.defiendRange.step;
5301         if(param.type === "Euler"){
5302             param.__step = _step;
5303             _step = param.target.value.order;
5304         }
5305         Object.defineProperty(param, 'step', {
5306             get: () => {
5307                 return _step;
5308             },
5309 
5310             set: v => {
5311                 if(param.type === "Euler"){
5312                     //如果想修改 Euler 控件的range.step: 'step: 0.01';
5313                     let i = v.indexOf(':');
5314                     if(i !== -1 && UTILS.removeSpaceSides(v.substr(0, i)) === 'step'){
5315                         let step = parseFloat(v.substr(i+1));
5316                         if(isNaN(step) === false) param.__step = step;
5317                         
5318                     }
5319 
5320                     else{
5321                         param.target.value.order = UTILS.removeSpaceSides(v).toUpperCase(); //去两边空格 并 转为大写字母
5322                         stepVal.clear().text(param.target.value.order, this._conColor, this._fontSIze, "center", "center");
5323                     }
5324                     
5325                 }
5326 
5327                 else{
5328                     v = parseFloat(v);
5329                     if(isNaN(v) === true) return;
5330                     _step = v;
5331                     stepVal.clear().text(this._numToStr(v), this._conColor, this._fontSIze, "center", "center");
5332                     
5333                 }
5334 
5335             }
5336 
5337         });
5338 
5339         progress._min = param.min;
5340         progress._max = param.max;
5341         progress._width = progress.box.w;
5342         progress._updateCursor = value => {
5343             if(value < progress._min) value = progress._min;
5344             else if(value > progress._max) value = progress._max;
5345             progress.box.w = (value - progress._min) / (progress._max - progress._min) * progress._width;
5346             progressCursor.box.x = progress.box.maxX() - progressCursor.box.w/2;
5347 
5348         }
5349 
5350         const titleWidth = this._cretaeTitle(data.title, data.explain, y),
5351         colon = new CanvasAnimate(this.img_colon).pos(titleWidth, y),
5352         stepBor = this._bindHover(new CanvasAnimateImages([this.img_buttonStepA, this.img_buttonStepB]), y)
5353         .pos(colon.box.x + this._conWidth - this._height, y + (this._height - this.img_buttonStepA.height) / 2),
5354         stepVal = new CanvasAnimateCustom().size(stepBor.box.w, stepBor.box.h).pos(stepBor.box.x, stepBor.box.y),
5355 
5356         setStepVal = value => {
5357             param.step = value;
5358             param.redraw();
5359         }
5360 
5361         this.cae.add(stepBor, "click", () => this._showInput(y, setStepVal, param.type !== "Euler" ? param.step : param.target.value.order));
5362 
5363         this.list.push(colon, stepVal, stepBor);
5364         param.step = _step;
5365         
5366     }
5367 
5368     createNumbers(data, v, y){
5369         var isDownCursor = false;
5370 
5371         const disableCA = new CanvasAnimate(this.img_dis).pos(0, y),
5372         progress = new CanvasAnimate(this.img_progress).pos(this.img_colon.width + this._titleWidth, y + this._height - this.img_progress.height - 1),
5373         progressCursor = new CanvasAnimateImages([this.img_numButSignA, this.img_numButSignB]).pos(progress.box.x, progress.box.y - this.img_numButSignA.height / 2),
5374 
5375         //create number input
5376         target = this._getValueUrl(data.valueUrl), 
5377         param = this._getEventParam(data, y, target, disableCA),
5378         inputX = this._createNumber(data, progress, param, progress.box.x, y, v === "number" ? target.object : target.value, v === "number" ? target.name : "x"),
5379         inputY = (v === "Vector2" || v === "Vector3" || v === "Euler") ? this._createNumber(data, progress, param, inputX.maxX, y, target.value, "y") : null,
5380         inputZ = (v === "Vector3" || v === "Euler") ? this._createNumber(data, progress, param, inputY.maxX, y, target.value, "z") : null,
5381 
5382         //event
5383         onmove = event => {
5384             let is = false;
5385             v = (event.pageX - this.domElementRect.x - progress.box.x) / progress._width * (progress._max - progress._min) + progress._min;
5386 
5387             //setValue 本来就根据param.min.max限制值, 这里是根据 progress.min.max 限制值
5388             if(v < progress._min) v = progress._min;
5389             else if(v > progress._max) v = progress._max;
5390 
5391             if(inputX.sign.i === 1){
5392                 if(inputX.setValue(v, false) === true && is !== true) is = true;
5393                 
5394             }
5395 
5396             if(inputY !== null && inputY.sign.i === 1){
5397                 if(inputY.setValue(v, false) === true && is !== true) is = true;
5398 
5399             }
5400 
5401             if(inputZ !== null && inputZ.sign.i === 1){
5402                 if(inputZ.setValue(v, false) === true && is !== true) is = true;
5403 
5404             }
5405 
5406             if(is === true){
5407                 this._onChanges(data, param);
5408                 param.redraw();
5409             }
5410 
5411         },
5412 
5413         onup = ()=>{
5414             this.cae.remove(this._globalCA, "move", onmove);
5415             this.cae.remove(this._globalCA, "up", onup);
5416             this.cae.remove(progressCursor, "move", onmove);
5417             this.cae.remove(progressCursor, "up", onup);
5418             isDownCursor = false;
5419         };
5420 
5421         //bug: 划入时 点击游标会触发move事件(猜测: 可能是在down里面绑定事件带来的延迟)
5422         this.cae.add(progressCursor, "down", () => {
5423             if(param.disable === true) return;
5424             isDownCursor = true;
5425             //onmove(event);
5426             this.cae.add(this._globalCA, "move", onmove);
5427             this.cae.add(this._globalCA, "up", onup);
5428             this.cae.add(progressCursor, "move", onmove);
5429             this.cae.add(progressCursor, "up", onup);
5430 
5431         });
5432 
5433         this.cae.add(progressCursor, "out", ()=>{
5434             this.domElement.title = "";
5435             this.domElement.style.cursor = "";
5436             progressCursor.set(0);
5437             param.redraw();
5438         });
5439 
5440         this.cae.add(progressCursor, "over", ()=>{
5441             this.domElement.title = progress._min + " <-点进度条调整此范围-> " + progress._max;
5442             this.domElement.style.cursor = "pointer";
5443             progressCursor.set(1);
5444             param.redraw();
5445         });
5446 
5447         this.cae.add(progress, 'up', ()=>{
5448             if(isDownCursor !== false) return;
5449             this._showInput(y, v=>{
5450                 let i = v.indexOf(',');
5451                 if(i !== -1){
5452                     let a = parseFloat(v.substr(0, i)), b = parseFloat(v.substr(i+1));
5453                     if(isNaN(a) === false && isNaN(b) === false && a >= param.min && a < param.max && b > param.min && b <= param.max && a < b){
5454                         progress._min = a;
5455                         progress._max = b;
5456                     }
5457                     else{
5458                         progress._min = param.min;
5459                         progress._max = param.max;
5460                     }
5461 
5462                     progress._updateCursorAtSign();
5463                     param.redraw();
5464                 }
5465             }, progress._min+","+progress._max);
5466         });
5467 
5468         //param 特有属性 (type: "number" || "Vector2" || "Vector3" || "Euler")
5469         param.type = v;
5470 
5471         //create number scene
5472         this.createNumber(data, y, param, progress, progressCursor);
5473 
5474         //init
5475         progress._updateCursorAtSign = () => {
5476             if(inputZ !== null && inputZ.sign.i === 1) progress._updateCursor(target.value.z);
5477             else if(inputY !== null && inputY.sign.i === 1) progress._updateCursor(target.value.y);
5478             else if(inputX.sign.i === 1) progress._updateCursor(typeof target.value === "number" ? target.value : target.value.x);
5479 
5480         }
5481         
5482         progressCursor.set(0);
5483         inputX.sign.set(1);
5484         inputX.toValue(); //inputX.setValue(v === "number" ? target.value : target.value.x, false);
5485         if(inputY !== null) inputY.toValue(); //if(inputY !== null) inputY.setValue(target.value.y);
5486         if(inputZ !== null) inputZ.toValue(); //if(inputZ !== null) inputZ.setValue(target.value.z);
5487         this.list.push(progress, progressCursor, disableCA);
5488         
5489     }
5490 
5491     createFileImage(data, y){
5492         var imageInfo = "";
5493         const target = this._getValueUrl(data.valueUrl),
5494         titleWidth = this._cretaeTitle(data.title, data.explain, y),
5495         colon = new CanvasAnimate(this.img_colon).pos(titleWidth, y),
5496         conA = new CanvasAnimateCustom().size(this._conWidth - colon.box.w - this._height * 3, this._height).pos(colon.box.maxX(), y),
5497         butReset = this._bindHover(new CanvasAnimateImages([this.img_buttonResetA, this.img_buttonResetB]), y, "reset").pos(conA.box.maxX(), y),
5498         conBG = new CanvasAnimate(this.img_colorBG).pos(butReset.box.maxX(), y),
5499         conB = new CanvasAnimateCustom().size(this._height, this._height).pos(conBG.box.x, y),
5500         but = this._bindHover(new CanvasAnimateImages([this.img_buttonEditA, this.img_buttonEditB]), y).pos(conB.box.maxX(), y), 
5501         disableCA = new CanvasAnimate(this.img_dis).pos(0, y),
5502         eventParam = this._getEventParam(data, y, target, disableCA),
5503         
5504         setValue = value => {
5505             if(this.isCanvasImage(value) === true){
5506                 let p = value.width / value.height, _w = 0, _h = 0;
5507                 if(p < 1){
5508                     _w = p * conB.box.w; 
5509                     _h = conB.box.h;
5510                 }
5511                 else{
5512                     _w = conB.box.w;
5513                     _h = conB.box.w / p;
5514                 }
5515 
5516                 conB.clear().drawImage(value, 0, 0, value.width, value.height, (conB.box.w - _w) / 2, (conB.box.h - _h) / 2, _w, _h);
5517 
5518                 imageInfo = value.width + " * " + value.height + " | " + this._numToStr(_w / value.width);
5519                 conA.clear().text(imageInfo, this._conColor, this._fontSIze, 0, "center");
5520 
5521             }
5522 
5523             else{
5524                 imageInfo = "";
5525                 conA.clear();
5526                 conB.clear();
5527                 value = null;
5528             }
5529             
5530             if(target.value !== value){
5531                 target.value = value;
5532                 this._onChanges(data, eventParam);
5533             }
5534 
5535             this._redrawTarget(y);
5536         }
5537 
5538         //event
5539         this.cae.add(conB, "click", () => CanvasAnimateUI.downloadImage(setValue));
5540         this.cae.add(butReset, "click", setValue);
5541         this.cae.add(but, "click", () => this._showImageViewer(y, target.value));
5542         
5543         this.cae.add(conA, "out", this._funcTitleOut);
5544         this.cae.add(conA, "over", ()=>this.domElement.title = imageInfo);
5545         
5546         //update
5547         eventParam.update = ()=> setValue(target.value);
5548 
5549         //init
5550         if(this.isCanvasImage(target.value) === false && target.value !== null) target.value = null;
5551         setValue(target.value);
5552         this.list.push(colon, conA, butReset, conBG, conB, but, disableCA);
5553         
5554     }
5555 
5556     createFileJSON(data, y){
5557 
5558         console.log("CanvasAnimateUI: 暂不支持 type = json");
5559 
5560     }
5561 
5562 }
5563 
5564 
5565 
5566 
5567 /** ProgressView 进度条视图
5568 parameter: 
5569     option = {
5570     
5571         bgColor: Color; //背景色
5572         scrollColor: Color; //滚动条颜色
5573 
5574         textBar: Bool; //进度条是否展示文本
5575         textColor: Color; //字体颜色(.textBar 为true才有效)
5576         textSize: Number; //字体大小(.textBar 为true才有效)
5577 
5578     }
5579 
5580 attribute:
5581 method:
5582     set(p: Number, u: Bool): undefined; //设置进度条的进度 p: now / all, u: 是否更新视图 默认false;
5583     setWidth(v: Number, u: Bool): undefined; //
5584     exit(): undefined;
5585 
5586 demo:
5587 
5588 */
5589 class ProgressView extends CanvasAnimateRender{
5590 
5591     constructor(option){
5592         super(option);
5593         
5594         const width = this.domElement.width, height = this.domElement.height,
5595         cac = new CanvasAnimateCustom().size(width, height).rect().fill(option.bgColor),
5596         scroll = new CanvasAnimateCustom().size(width, height).rect().fill(option.scrollColor);
5597         this.list.push(cac, scroll);
5598 
5599         option.textBar = typeof option.textBar === "boolean" ? option.textBar : false;
5600         if(option.textBar === true){
5601             this._textSize = option.textSize || 12;
5602             this._textColor = option.textColor || "#000000";
5603             this._textBar = new CanvasAnimateCustom().size(width, height);
5604             this.list.push(this._textBar);
5605             
5606         }
5607 
5608         else this._textBar = null;
5609         
5610         this._scroll = scroll;
5611         this._width = width;
5612 
5613         this.initList();
5614         this.set(0);
5615         
5616     }
5617 
5618     set(p, u){
5619         p = this._width * p;
5620         p = p < 1 ? 1 : p > this._width ? this._width : p;
5621 
5622         if(this._scroll.box.w !== p){
5623             this._scroll.box.w = p;
5624             if(this._textBar !== null){
5625                 this._textBar.clear().text((p/this._width*100).toFixed(2) + "%", this._textColor, this._textSize, "center", "center");
5626             }
5627             if(u === true) this.redraw();
5628         }
5629         
5630     }
5631 
5632     setWidth(v, u){
5633         this._scroll.box.w = v;
5634         if(this._textBar !== null){
5635             this._textBar.clear().text((v/this._width*100).toFixed(2) + "%", this._textColor, this._textSize, "center", "center");
5636         }
5637         if(u === true) this.redraw();
5638 
5639     }
5640 
5641     exit(){
5642         if(this.domElement.parentElement) this.domElement.parentElement.removeChild(this.domElement);
5643         
5644     }
5645 
5646 }
5647 
5648 
5649 
5650 
5651 /* PolygonTest 调试类 Polygon (可视化调试: 多边形之合并)
5652 
5653     无任何属性, new就完事了!
5654 
5655 */
5656 class PolygonTest{
5657 
5658     constructor(){
5659         const path = [], box = new Box(),
5660 
5661         car = new CanvasAnimateRender({
5662             width: window.innerWidth, 
5663             height: window.innerHeight,
5664         }).render(), 
5665     
5666         cab = car.add(new CanvasAnimateBox()).size(car.box.w, car.box.h, true);
5667         
5668         function draw(obj){
5669             console.log(obj);
5670     
5671             target._redraw();
5672     
5673             obj.forEach((v, i) => {
5674                 const str = String(i), 
5675                 size = cab.textWidth(str, 20), 
5676                 width = size < 20 ? 20 : size;
5677                 
5678                 cab.rect(2, box.set(v.x, v.y, width, width), 2).fill('#fff');
5679                 cab.text(str, '#0000ff', box.pos(v.x + (width - size)/2, v.y));
5680     
5681                 if(v.x1 === undefined) cab.stroke('#ffff00');
5682                 
5683             });
5684     
5685             car.redraw();
5686         }
5687     
5688     
5689         const target = {
5690             title: '测试多边形之合并(此类并不完美)',
5691             polygonA: null,
5692             polygonB: null,
5693             centerA: new Box(),
5694             centerB: new Box(),
5695             length: 0,
5696             mergeType: 0,
5697     
5698             merge(){
5699                 if(this.polygonA === null || this.polygonB === null) return console.warn('必须已生成两个多边形');
5700                 if(this.mergeType === 0) draw(this.polygonA.merge(this.polygonB));
5701                 else draw(this.polygonB.merge(this.polygonA));
5702                 
5703             },
5704     
5705             setPolygonA(){
5706                 this.polygonA = new Polygon(path.concat());
5707                 this.centerA.setFromPolygon(this.polygonA, false);
5708                 update_offsetNum();
5709             },
5710     
5711             setPolygonB(){
5712                 this.polygonB = new Polygon(path.concat());
5713                 this.centerB.setFromPolygon(this.polygonB, false);
5714                 update_offsetNum();
5715             },
5716     
5717             generate(){
5718                 if(path.length < 6) return console.warn('路径至少需要3个点');
5719     
5720                 if(this.polygonA !== null && this.polygonB !== null){
5721                     const _path = path.concat();
5722                     this.clear();
5723                     _path.forEach(v => path.push(v));
5724                     this.generate();
5725                     return;
5726                 }
5727     
5728                 else{
5729                     cab.path(path, true);
5730     
5731                     if(this.polygonA === null) this.setPolygonA();
5732                     else this.setPolygonB();
5733                     path.length = 0;
5734                     
5735                 }
5736                 
5737                 this._redraw();
5738                 car.redraw();
5739             },
5740     
5741             clear(){
5742                 path.length = 0;
5743                 this.polygonA = this.polygonB = null;
5744                 this.centerA.set(0,0,0,0);
5745                 this.centerB.set(0,0,0,0);
5746                 cab.clear();
5747                 car.clear();
5748             },
5749     
5750             clearPath(){
5751                 path.length = 0;
5752                 cab.clear();
5753                 this._redraw();
5754                 car.redraw();
5755             },
5756     
5757             _redraw(){
5758                 if(this.polygonA !== null){
5759                     cab.clear().path(this.polygonA.path).stroke('#00ff00', 1);
5760                     this.sign(this.polygonA.path, 'rgba(0,255,0,1)', 'rgba(0,255,0,0.6)');
5761                     if(this.polygonB !== null){
5762                         cab.path(this.polygonB.path).stroke('#ff0000', 1);
5763                         this.sign(this.polygonB.path, 'rgba(255,0,0,1)', 'rgba(255,0,0,0.6)');
5764                     }
5765                     
5766                 }
5767                 
5768             },
5769     
5770             //标记线的第一和第二个点
5771             sign(path, color1, color2){
5772                 cab.rect(5, box.set(path[0]-5, path[1]-5, 10, 10), 2).fill(color1);
5773                 cab.rect(5, box.set(path[2]-5, path[3]-5, 10, 10), 2).fill(color2);
5774             },
5775     
5776             //偏移路径
5777             offsetTimer: new Timer(()=>target.offset(), 300, 1, false),
5778             offsetNum: new Point(0, 0),
5779             offsetTarget: 'polygonA',
5780             offsetVN: 'x',
5781             offsetPoint: new Point(),
5782             offset(){
5783                 if(this[this.offsetTarget] === null) return;
5784                 path.length = 0;
5785                 
5786                 const pathNew = path, pathOld = this[this.offsetTarget].path, point = this.offsetPoint, 
5787                 center = this.offsetTarget === 'polygonA' ? 'centerA' : 'centerB',
5788                 x = this.offsetNum[this.offsetVN] * car.box[this.offsetVN === 'x' ? 'w' : 'h'];
5789                 
5790                 for(let k = 0, len = pathOld.length; k < len; k+=2){
5791                     point.set(pathOld[k], pathOld[k+1]);
5792                     point[this.offsetVN] = point[this.offsetVN] - this[center][this.offsetVN] + x;
5793                     pathNew.push(point.x, point.y);
5794                 }
5795     
5796                 this[this.offsetTarget === 'polygonA' ? 'setPolygonA' : 'setPolygonB']();
5797                 path.length = 0;
5798                 this._redraw();
5799                 car.redraw();
5800             },
5801     
5802             //克隆
5803             clone(){
5804                 if(this[this.offsetTarget] === null) return;
5805                 path.length = 0;
5806                 this[this.offsetTarget].path.forEach(v => path.push(v));
5807                 this[this.offsetTarget === 'polygonA' ? 'setPolygonB' : 'setPolygonA']();
5808                 
5809                 path.length = 0;
5810                 this._redraw();
5811                 car.redraw();
5812                 
5813             },
5814     
5815         },
5816     
5817         lineData = {type: 'line', value: '.title'},
5818     
5819         data = [
5820             {
5821                 title: '项目',
5822                 valueUrl: '.title',
5823             },
5824             {
5825                 explain: '路径长度',
5826                 title: 'length',
5827                 valueUrl: '.length',
5828                 disable: true,
5829             },
5830     
5831             {
5832                 explain: '生成多边形',
5833                 title: 'generate',
5834                 valueUrl: '.generate',
5835             },
5836     
5837             lineData,
5838             {
5839                 title: 'mergeType',
5840                 valueUrl: '.mergeType',
5841                 selectList: [{name: 'lineB to lineA', value: 0}, {name: 'lineA to lineB', value: 1}]
5842             },
5843     
5844             {
5845                 explain: '合并多边形',
5846                 title: 'merge',
5847                 valueUrl: '.merge',
5848             },
5849             lineData,
5850     
5851             {
5852                 title: 'line',
5853                 valueUrl: '.offsetTarget',
5854                 selectList: [{name: 'lineA', value: 'polygonA'}, {name: 'lineB', value: 'polygonB'}],
5855                 onChange: ()=> update_offsetNum(),
5856             },
5857     
5858             {
5859                 title: 'clone',
5860                 valueUrl: '.clone',
5861                 explain: '克隆形状后它们会重叠, 可以通过offset控件移动它们',
5862             },
5863     
5864             {
5865                 title: 'offset',
5866                 valueUrl: '.offsetNum',
5867                 range: {min: 0, max: 1, step: 0.01},
5868                 onChange: v => {
5869                     v.scope.target.offsetVN = v.valueName;
5870                     v.scope.target.offsetTimer.start();
5871                 },
5872             },
5873             lineData,
5874     
5875             {
5876                 explain: '清理路径和多边形',
5877                 title: 'clearAll',
5878                 valueUrl: '.clear',
5879             },
5880     
5881             {
5882                 explain: '清理路径',
5883                 title: 'clearPath',
5884                 valueUrl: '.clearPath',
5885             },
5886     
5887         ],
5888     
5889         ui = new CanvasAnimateUI(target, data, document.body, {
5890             autoHeight: true,
5891             width: 250,
5892             globalUpdate: true,
5893             defiendRange: {min: 0, max: car.box.w, step: 1},
5894             style: `
5895                 position: absolute;
5896                 right: 2px;
5897                 top: 2px;
5898                 background: #fff;
5899             `,
5900         }).initUI(),
5901     
5902     
5903         ui_length = ui.getView(ui.getData('.length')),
5904         update_length = function (){
5905             target.length = path.length; //更新值
5906             ui_length.update[0](); //更新ca
5907             ui_length.redraw(); //更新画布
5908         },
5909         
5910         ui_offsetNum = ui.getView(ui.getData('.offsetNum')),
5911         update_offsetNum = function (){
5912             const center = target.offsetTarget === 'polygonA' ? 'centerA' : 'centerB';
5913             target.offsetNum.set(target[center].x / car.box.w, target[center].y / car.box.h);  //更新值
5914             ui_offsetNum.update[0](); //更新ca
5915             ui_offsetNum.update[1](); //更新ca
5916             ui_offsetNum.redraw(); //更新画布
5917         };
5918     
5919     
5920         //event
5921         const cae = new CanvasAnimateEvent(car), 
5922         eventBox = new CanvasAnimate();
5923         eventBox.box.copy(car.box);
5924     
5925         cae.add(eventBox, 'click', event => {
5926             if(path.length >= 2) cab.line(path[path.length - 2], path[path.length - 1], event.pageX, event.pageY).stroke('#ffff00', 1);
5927             else cab.rect(20/2, box.size(20, 20).pos(event.pageX - 10, event.pageY - 10), 2).fill('#ffff00');
5928     
5929             path.push(event.pageX, event.pageY);
5930             car.redraw();
5931             update_length();
5932         });
5933     
5934     
5935     }
5936 
5937 }

 

标签:box,Canvas,return,ca,js,画布,._,null,data
来源: https://www.cnblogs.com/weihexinCode/p/16368449.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有