import { Component, ElementRef, EventEmitter, HostListener, Input, OnChanges, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'; import { Subject } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; import { EditorItem, HotZoneImageItem, Label, MySprite } from './Unit'; import TWEEN from '@tweenjs/tween.js'; @Component({ selector: 'app-custom-hot-zone', templateUrl: './custom-hot-zone.component.html', styleUrls: ['./custom-hot-zone.component.scss'] }) export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges { _bgItem = null; @Input() set bgItem(v) { this._bgItem = v; this.init(); } get bgItem() { return this._bgItem; } @Input() imgItemArr = null; @Input() hotZoneItemArr = null; @Input() hotZoneArr = null; @Output() save = new EventEmitter(); @ViewChild('canvas') canvas: ElementRef; @ViewChild('wrap') wrap: ElementRef; @HostListener('window:resize', ['$event']) onResize(event) { this.g_winResizeEventStream.next(); } g_winResizeEventStream = new Subject(); canvasWidth = 1280; canvasHeight = 720; canvasBaseW = 1280; canvasBaseH = 720; mapScale = 1; ctx; mx; my; // 点击坐标 // 资源 // rawImages = new Map(res); // 声音 bgAudio = new Audio(); images = new Map(); animationId: any; // winResizeEventStream = new Subject(); canvasLeft; canvasTop; renderArr; imgArr = []; oldPos; curItem; bg: MySprite; changeSizeFlag = false; changeTopSizeFlag = false; changeRightSizeFlag = false; hotZoneChanged = false; scale = 1; constructor(private el: ElementRef) { } ngOnInit() { if(!this.bgItem.url){ this.bgItem.url = "def_window" } this.initListener(); this.update(); } ngOnDestroy() { window.cancelAnimationFrame(this.animationId); } ngOnChanges() { } onBackgroundUploadSuccess(e) { this.bgItem.url = e.url; this.refreshBackground(() => { this.autoSave() }); } refreshBackground(callBack = null) { if (!this.bg) { this.bg = new MySprite(this.ctx); this.renderArr.push(this.bg); } const bg = this.bg; if (this.bgItem.url) { bg.load(this.bgItem.url).then(() => { const rate1 = this.canvasWidth / bg.width; const rate2 = this.canvasHeight / bg.height; const rate = Math.min(rate1, rate2); bg.setScaleXY(rate); bg.x = this.canvasWidth / 2; bg.y = this.canvasHeight / 2; if (callBack) { callBack(); } }).catch((error) => { console.log(error) }) } } addBtnClick() { const item = this.getHotZoneItem(); this.hotZoneArr.push(item); this.refreshHotZoneId(); this.autoSave() } handleMoveItemUp(event, index) { if (index != 0) { this.hotZoneArr[index] = this.hotZoneArr.splice(index - 1, 1, this.hotZoneArr[index])[0]; } else { this.hotZoneArr.push(this.hotZoneArr.shift()); } this.autoSave() } handleMoveItemDown(event, index) { if (index != this.hotZoneArr.length - 1) { this.hotZoneArr[index] = this.hotZoneArr.splice(index + 1, 1, this.hotZoneArr[index])[0]; } else { this.hotZoneArr.unshift(this.hotZoneArr.splice(index, 1)[0]); } this.autoSave() } onImgUploadSuccessByImg(e, item) { item.pic_url = e.url; item.image_url = e.url; item.initNew((w, h) => { item.setScaleXY(Math.min(100 / w, 150 / h)) }) this.refreshImage(item); this.autoSave() } onAudioUploadSuccess(e, item) { item.audio_url = e.url; this.autoSave() } refreshImage(img) { this.hideAllLineDash(); img.picItem = this.getPicItem(img); this.refreshImageId(); } refreshHotZoneId() { for (let i = 0; i < this.hotZoneArr.length; i++) { this.hotZoneArr[i].index = i; } } refreshHotZoneText(item) { item.initNew() } refreshImageId() { for (let i = 0; i < this.imgArr.length; i++) { this.imgArr[i].id = i; if (this.imgArr[i].picItem) { this.imgArr[i].picItem.text = 'Image-' + (i + 1); } } } getHotZoneItem(saveData = null, newRect?) { const itemW = 200; const itemH = 200; const item = new HotZoneImageItem(this.ctx); if (saveData) { item.init(saveData.media.image_url, saveData.media.text, saveData.media.type, (w, h) => { const saveRect = saveData.rect; item.scaleX = (saveData.rect.width / saveData.scale) / w; item.scaleY = (saveData.rect.height / saveData.scale) / h; item.x = saveRect.x / saveData.scale + newRect.x item.y = saveRect.y / saveData.scale + newRect.y }); item.image_url = saveData.media.image_url item.text = saveData.media.text item.type = saveData.media.type } else { item.init("assets/play/default/images/bg_200_200.png", null, "Text", () => { item.image_url = "assets/play/default/images/bg_200_200.png" item.x = this.canvasWidth / 2 - 100; item.y = this.canvasHeight / 2 - 100; }, () => this.autoSave()); } item.anchorX = 0.5; item.anchorY = 0.5; return item; } getPicItem(img, saveData = null) { const item = new EditorItem(this.ctx); item.load(img.pic_url).then(img => { let maxW, maxH; if (this.bg) { maxW = this.bg.width * this.bg.scaleX; maxH = this.bg.height * this.bg.scaleY; } else { maxW = this.canvasWidth; maxH = this.canvasHeight; } let scaleX = maxW / 3 / item.width; let scaleY = maxH / 3 / item.height; if (item.height * scaleX < this.canvasHeight) { item.setScaleXY(scaleX); } else { item.setScaleXY(scaleY); } item.x = this.canvasWidth / 2; item.y = this.canvasHeight / 2; if (saveData) { const saveRect = saveData.rect; item.setScaleXY(saveRect.width / item.width); item.x = saveRect.x + saveRect.width / 2; item.y = saveRect.y + saveRect.height / 2; } else { item.showLineDash(); } this.autoSave() }) return item; } onAudioUploadSuccessByImg(e, img) { img.audio_url = e.url; this.autoSave() } deleteItem(e, i) { this.hotZoneArr.splice(i, 1); this.refreshHotZoneId(); this.autoSave() } init() { this.initData(); this.initCtx(); this.initItem(); } initItem() { if (!this.bgItem) { this.bgItem = {}; } else { this.refreshBackground(() => { if (!this.hotZoneItemArr) { this.hotZoneItemArr = []; } else { this.initHotZoneArr(); } }); } } initHotZoneArr() { let curBgRect; if (this.bg) { curBgRect = this.bg.getBoundingBox(); } else { curBgRect = { x: 0, y: 0, width: this.canvasWidth, height: this.canvasHeight }; } let oldBgRect = this.bgItem.rect; if (!oldBgRect) { oldBgRect = curBgRect; } const rate = curBgRect.width / oldBgRect.width; this.hotZoneArr = []; const arr = this.hotZoneItemArr.concat(); for (let i = 0; i < arr.length; i++) { const data = JSON.parse(JSON.stringify(arr[i])); data.rect.x *= rate; data.rect.y *= rate; data.rect.width *= rate; data.rect.height *= rate; // data.rect.x += curBgRect.x; // data.rect.y += curBgRect.y; const item = this.getHotZoneItem(data, { x: curBgRect.x, y: curBgRect.y }); // c//onsole.log("初始化存储数据", data) this.hotZoneArr.push(item); } this.refreshHotZoneId(); } initImgArr() { let curBgRect; if (this.bg) { curBgRect = this.bg.getBoundingBox(); } else { curBgRect = { x: 0, y: 0, width: this.canvasWidth, height: this.canvasHeight }; } let oldBgRect = this.bgItem.rect; if (!oldBgRect) { oldBgRect = curBgRect; } const rate = curBgRect.width / oldBgRect.width; this.imgArr = []; const arr = this.imgItemArr.concat(); for (let i = 0; i < arr.length; i++) { const data = JSON.parse(JSON.stringify(arr[i])); const img = { pic_url: data.pic_url }; data.rect.x *= rate; data.rect.y *= rate; data.rect.width *= rate; data.rect.height *= rate; data.rect.x += curBgRect.x; data.rect.y += curBgRect.y; img['picItem'] = this.getPicItem(img, data); img['audio_url'] = arr[i].audio_url; this.imgArr.push(img); } this.refreshImageId(); } initData() { this.canvasWidth = this.wrap.nativeElement.clientWidth; this.canvasHeight = this.wrap.nativeElement.clientWidth * (498 / 892); this.scale = 892 * 0.95 / this.wrap.nativeElement.clientWidth this.mapScale = this.canvasWidth / this.canvasBaseW; this.renderArr = []; this.bg = null; this.imgArr = []; this.hotZoneArr = []; } initCtx() { this.ctx = this.canvas.nativeElement.getContext('2d'); this.canvas.nativeElement.width = this.canvasWidth; this.canvas.nativeElement.height = this.canvasHeight; } mapDown(event) { this.oldPos = { x: this.mx, y: this.my }; const arr = this.hotZoneArr; for (let i = arr.length - 1; i >= 0; i--) { const item = arr[i]; if (item) { if (this.checkClickTarget(item)) { if (item.lineDashFlag && this.checkClickTarget(item.arrow)) { if(item.type == "Text"){ return } this.changeItemSize(item); } else if (item.lineDashFlag && this.checkClickTarget(item.arrowTop)) { if(item.type == "Text"){ return } this.changeItemTopSize(item); } else if (item.lineDashFlag && this.checkClickTarget(item.arrowRight)) { if(item.type == "Text"){ return } this.changeItemRightSize(item); } else { this.changeCurItem(item); } this.hotZoneChanged = true; return; } } } } mapMove(event) { if (!this.curItem) { return; } if (this.changeSizeFlag) { this.changeSize(); } else if (this.changeTopSizeFlag) { this.changeTopSize(); } else if (this.changeRightSizeFlag) { this.changeRightSize(); } else { let addX = this.mx - this.oldPos.x; let addY = this.my - this.oldPos.y; this.curItem.x += addX; this.curItem.y += addY; } this.oldPos = { x: this.mx, y: this.my }; } mapUp(event) { this.curItem = null; this.changeSizeFlag = false; this.changeTopSizeFlag = false; this.changeRightSizeFlag = false; } changeSize() { const rect = this.curItem.getBoundingBox(); let distance = this.my - rect.y; let lenW = this.mx - rect.x; let lenH = rect.height - distance let minLen = 20; let sx, sy; if (lenW < minLen) { lenW = minLen; } sx = lenW / this.curItem.width; if (lenH < minLen) { lenH = minLen; } sy = lenH / this.curItem.height; this.curItem.y = this.curItem.y + distance this.curItem.scaleX = sx; this.curItem.scaleY = sy; } changeTopSize() { const rect = this.curItem.getBoundingBox(); let lenH = rect.y - this.my + rect.height; this.curItem.y = this.my let minLen = 20; let s; if (lenH < minLen) { lenH = minLen; } s = lenH / this.curItem.height; this.curItem.setScaleXY(s); } changeRightSize() { const rect = this.curItem.getBoundingBox(); let lenW = this.mx - rect.x; let minLen = 20; let s; if (lenW < minLen) { lenW = minLen; } s = lenW / this.curItem.width; this.curItem.setScaleXY(s); } changeItemSize(item) { this.curItem = item; this.changeSizeFlag = true; } changeItemTopSize(item) { this.curItem = item; this.changeTopSizeFlag = true; } changeItemRightSize(item) { this.curItem = item; this.changeRightSizeFlag = true; } changeCurItem(item) { this.hideAllLineDash(); this.curItem = item; this.curItem.showLineDash(); } hideAllLineDash() { for (let i = 0; i < this.imgArr.length; i++) { if (this.imgArr[i].picItem) { this.imgArr[i].picItem.hideLineDash(); } } } update() { this.animationId = window.requestAnimationFrame(this.update.bind(this)); // 清除画布内容 this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight); for (let i = 0; i < this.renderArr.length; i++) { this.renderArr[i].update(this); } this.updateArr(this.hotZoneArr); TWEEN.update(); } updateArr(arr) { if (arr) { for (let i = 0; i < arr.length; i++) { arr[i].update(); } } } renderAfterResize() { // let offsetWidth = this.el.nativeElement.querySelector('#canvas-container').offsetWidth // this.el.nativeElement.querySelector('#canvas-container').style.height = '' + offsetWidth / 2 * 3 + 'px' this.canvasWidth = this.wrap.nativeElement.clientWidth; this.canvasHeight = this.wrap.nativeElement.clientHeight; this.init(); } initListener() { this.g_winResizeEventStream.pipe(debounceTime(500)).subscribe(data => { this.renderAfterResize(); }); if (this.IsPC()) { this.canvas.nativeElement.addEventListener('mousedown', (event) => { setMxMyByMouse(event); this.mapDown(event); }); this.canvas.nativeElement.addEventListener('mousemove', (event) => { setMxMyByMouse(event); this.mapMove(event); }); this.canvas.nativeElement.addEventListener('mouseup', (event) => { setMxMyByMouse(event); this.mapUp(event); }); const setMxMyByMouse = (event) => { this.mx = event.offsetX; this.my = event.offsetY; }; } else { this.canvas.nativeElement.addEventListener('touchstart', (event) => { setMxMyByTouch(event); this.mapDown(event); }); this.canvas.nativeElement.addEventListener('touchmove', (event) => { setMxMyByTouch(event); this.mapMove(event); }); this.canvas.nativeElement.addEventListener('touchend', (event) => { setMxMyByTouch(event); this.mapUp(event); }); this.canvas.nativeElement.addEventListener('touchcancel', (event) => { setMxMyByTouch(event); this.mapUp(event); }); const setMxMyByTouch = (event) => { if (event.touches.length <= 0) { return; } if (this.canvasLeft == null) { setParentOffset(); } this.mx = event.touches[0].pageX - this.canvasLeft; this.my = event.touches[0].pageY - this.canvasTop; }; const setParentOffset = () => { const rect = this.canvas.nativeElement.getBoundingClientRect(); this.canvasLeft = rect.left; this.canvasTop = rect.top; }; } } IsPC() { if (window['ELECTRON']) { return false; // 封装客户端标记 } if (document.body.ontouchstart !== undefined) { return false; } else { return true; } } checkClickTarget(target) { const rect = target.getBoundingBox(); if (this.checkPointInRect(this.mx, this.my, rect)) { return true; } return false; } checkPointInRect(x, y, rect) { if (x >= rect.x && x <= rect.x + rect.width) { if (y >= rect.y && y <= rect.y + rect.height) { return true; } } return false; } autoSave() { // console.log("Auto save") const bgItem = this.bgItem; if (this.bg) { bgItem['rect'] = this.bg.getBoundingBox(); } else { bgItem['rect'] = { x: 0, y: 0, width: Math.round(this.canvasWidth * 100) / 100, height: Math.round(this.canvasHeight * 100) / 100 }; } const hotZoneItemArr = []; const hotZoneArr = this.hotZoneArr; for (let i = 0; i < hotZoneArr.length; i++) { const hotZoneItem = { index: hotZoneArr[i].index, }; hotZoneItem['rect'] = hotZoneArr[i].getBoundingBox(); const currentX = hotZoneItem['rect'].x const currentY = hotZoneItem['rect'].y hotZoneItem['media'] = {} hotZoneItem['scale'] = this.scale hotZoneItem['rect'].x = (Math.round((currentX - bgItem['rect'].x) * 100) / 100) * this.scale; hotZoneItem['rect'].y = (Math.round((currentY - bgItem['rect'].y) * 100) / 100) * this.scale; hotZoneItem['rect'].width = (Math.round((hotZoneItem['rect'].width) * 100) / 100) * this.scale; hotZoneItem['rect'].height = (Math.round((hotZoneItem['rect'].height) * 100) / 100) * this.scale; hotZoneItem['media'].image_url = hotZoneArr[i].image_url hotZoneItem['media'].text = hotZoneArr[i].text hotZoneItem['media'].type = hotZoneArr[i].type // hotZoneItem['media'].audio_url = hotZoneArr[i].audio_url ? hotZoneArr[i].audio_url : "" // hotZoneItem['media'].card_audio_url = hotZoneArr[i].card_audio_url ? hotZoneArr[i].card_audio_url : "" hotZoneItemArr.push(hotZoneItem); } this.save.emit({ bgItem, hotZoneItemArr }); this.hotZoneChanged = false; } saveClick() { // console.log("Saved") this.autoSave() } }