js封装原生画布 Canvas
作者:互联网
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