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, HotZoneImg, HotZoneItem, HotZoneLabel, Label, MySprite, removeItemFromArr} from './Unit'; import TWEEN from '@tweenjs/tween.js'; import {getMinScale} from "../../play/Unit"; import {tar} from "compressing"; @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 { @Input() imgItemArr = null; @Input() hotZoneItemArr = null; @Input() hotZoneArr = null; @Output() save = new EventEmitter(); @ViewChild('canvas', {static: true}) canvas: ElementRef; @ViewChild('wrap', {static: true}) wrap: ElementRef; @Input() isHasRect = true; @Input() isHasPic = true; @Input() isHasText = true; @Input() hotZoneFontObj = { size: 50, name: 'BRLNSR_1', color: '#8f3758' } @Input() defaultItemType = 'text'; @Input() hotZoneImgSize = 190; saveDisabled = true; canvasWidth = 1280; canvasHeight = 720; canvasBaseW = 1280; // @HostListener('window:resize', ['$event']) canvasBaseH = 720; mapScale = 1; ctx; mx; my; // 点击坐标 // 声音 bgAudio = new Audio(); images = new Map(); animationId: any; // 资源 // rawImages = new Map(res); winResizeEventStream = new Subject(); canvasLeft; canvasTop; renderArr; imgArr = []; oldPos; radioValue; curItem; bg: MySprite; changeSizeFlag = false; changeTopSizeFlag = false; changeRightSizeFlag = false; constructor() { } _bgItem = null; get bgItem() { return this._bgItem; } @Input() set bgItem(v) { this._bgItem = v; this.init(); } onResize(event) { this.winResizeEventStream.next(); } ngOnInit() { this.initListener(); // this.init(); this.update(); } ngOnDestroy() { window.cancelAnimationFrame(this.animationId); } ngOnChanges() { } onBackgroundUploadSuccess(e) { console.log('e: ', e); this.bgItem.url = e.url; this.refreshBackground(); } onItemImgUploadSuccess(e, item) { item.pic_url = e.url; this.loadHotZonePic(item.pic, e.url); } onItemAudioUploadSuccess(e, item) { item.audio_url = e.url; } 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(); } }); } } addBtnClick() { // this.imgArr.push({}); // this.hotZoneArr.push({}); const item = this.getHotZoneItem(); this.hotZoneArr.push(item); this.refreshItem(item); this.refreshHotZoneId(); } deleteBtnClick(index) { const item = this.hotZoneArr.splice(index, 1)[0]; removeItemFromArr(this.renderArr, item.pic); removeItemFromArr(this.renderArr, item.textLabel); this.refreshHotZoneId(); } onImgUploadSuccessByImg(e, img) { img.pic_url = e.url; this.refreshImage(img); } 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; if (this.hotZoneArr[i]) { this.hotZoneArr[i].title = 'item-' + (i + 1); } } } 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) { const itemW = 200; const itemH = 200; const item = new HotZoneItem(this.ctx); item.setSize(itemW, itemH); item.anchorX = 0.5; item.anchorY = 0.5; item.x = this.canvasWidth / 2; item.y = this.canvasHeight / 2; item.itemType = this.defaultItemType; if (saveData) { const saveRect = saveData.rect; item.scaleX = saveRect.width / item.width; item.scaleY = saveRect.height / item.height; item.x = saveRect.x + saveRect.width / 2; item.y = saveRect.y + saveRect.height / 2; } item.showLineDash(); const pic = new HotZoneImg(this.ctx); pic.visible = false; item['pic'] = pic; if (saveData && saveData.pic_url) { this.loadHotZonePic(pic, saveData.pic_url); } pic.x = item.x; pic.y = item.y; this.renderArr.push(pic); const textLabel = new HotZoneLabel(this.ctx); textLabel.fontSize = this.hotZoneFontObj.size; textLabel.fontName = this.hotZoneFontObj.name; textLabel.fontColor = this.hotZoneFontObj.color; textLabel.textAlign = 'center'; // textLabel.setOutline(); // console.log('saveData:', saveData); item['textLabel'] = textLabel; textLabel.setScaleXY(this.mapScale); if (saveData && saveData.text) { textLabel.text = saveData.text; textLabel.refreshSize(); } textLabel.x = item.x; textLabel.y = item.y; this.renderArr.push(textLabel); 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(); } }); return item; } onAudioUploadSuccessByImg(e, img) { img.audio_url = e.url; } deleteItem(e, i) { // this.imgArr.splice(i , 1); // this.refreshImageId(); this.hotZoneArr.splice(i, 1); this.refreshHotZoneId(); } radioChange(e, item) { item.itemType = e; this.refreshItem(item); // console.log(' in radioChange e: ', e); } refreshItem(item) { switch (item.itemType) { case 'rect': this.setRectState(item); break; case 'pic': this.setPicState(item); break; case 'text': this.setTextState(item); break; default: } } init() { this.initData(); this.initCtx(); this.initItem(); } initItem() { if (!this.bgItem) { this.bgItem = {}; } else { this.refreshBackground(() => { // if (!this.imgItemArr) { // this.imgItemArr = []; // } else { // this.initImgArr(); // } // console.log('aaaaa'); if (!this.hotZoneItemArr) { this.hotZoneItemArr = []; } else { this.initHotZoneArr(); } }); } } initHotZoneArr() { // console.log('this.hotZoneArr: ', this.hotZoneArr); 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; console.log('rate: ', rate); this.hotZoneArr = []; const arr = this.hotZoneItemArr.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); const item = this.getHotZoneItem(data); item.audio_url = data.audio_url; item.pic_url = data.pic_url; item.text = data.text; item.itemType = data.itemType; this.refreshItem(item); console.log('item: ', item); this.hotZoneArr.push(item); } this.refreshHotZoneId(); // this.refreshImageId(); } initImgArr() { console.log('this.imgItemArr: ', this.imgItemArr); 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; console.log('rate: ', rate); 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.clientHeight; 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}; for (let i = 0; i < this.hotZoneArr.length; i++) { const item = this.hotZoneArr[i]; let callback; let target; switch (item.itemType) { case 'rect': target = item; callback = this.clickedHotZoneRect.bind(this); break; case 'pic': target = item.pic; callback = this.clickedHotZonePic.bind(this); break; case 'text': target = item.textLabel; callback = this.clickedHotZoneText.bind(this); break; } if (this.checkClickTarget(target)) { callback(target); 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 { const addX = this.mx - this.oldPos.x; const addY = this.my - this.oldPos.y; this.curItem.x += addX; this.curItem.y += addY; } this.oldPos = {x: this.mx, y: this.my}; this.saveDisabled = true; } mapUp(event) { this.curItem = null; this.changeSizeFlag = false; this.changeTopSizeFlag = false; this.changeRightSizeFlag = false; } changeSize() { const rect = this.curItem.getBoundingBox(); let lenW = (this.mx - (rect.x + rect.width / 2)) * 2; let lenH = ((rect.y + rect.height / 2) - this.my) * 2; let minLen = 20; let s; if (lenW < lenH) { if (lenW < minLen) { lenW = minLen; } s = lenW / this.curItem.width; } else { if (lenH < minLen) { lenH = minLen; } s = lenH / this.curItem.height; } // console.log('s: ', s); this.curItem.setScaleXY(s); this.curItem.refreshLabelScale(); } changeTopSize() { const rect = this.curItem.getBoundingBox(); // let lenW = ( this.mx - (rect.x + rect.width / 2) ) * 2; let lenH = ((rect.y + rect.height / 2) - this.my) * 2; let minLen = 20; let s; // if (lenW < lenH) { // if (lenW < minLen) { // lenW = minLen; // } // s = lenW / this.curItem.width; // // } else { if (lenH < minLen) { lenH = minLen; } s = lenH / this.curItem.height; // } // console.log('s: ', s); this.curItem.scaleY = s; this.curItem.refreshLabelScale(); } changeRightSize() { const rect = this.curItem.getBoundingBox(); let lenW = (this.mx - (rect.x + rect.width / 2)) * 2; // let lenH = ( (rect.y + rect.height / 2) - this.my ) * 2; let minLen = 20; let s; if (lenW < minLen) { lenW = minLen; } s = lenW / this.curItem.width; this.curItem.scaleX = s; this.curItem.refreshLabelScale(); } 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() { if (!this.ctx) { return; } 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); } // for (let i = 0; i < this.imgArr.length; i++) { // const picItem = this.imgArr[i].picItem; // if (picItem) { // picItem.update(this); // } // } this.updateArr(this.hotZoneArr); this.updatePos() TWEEN.update(); } updateArr(arr) { if (arr) { for (let i = 0; i < arr.length; i++) { arr[i].update(); } } } renderAfterResize() { this.canvasWidth = this.wrap.nativeElement.clientWidth; this.canvasHeight = this.wrap.nativeElement.clientHeight; this.init(); } initListener() { this.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; } saveClick() { 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, pic_url: hotZoneArr[i].pic_url, text: hotZoneArr[i].text, audio_url: hotZoneArr[i].audio_url, itemType: hotZoneArr[i].itemType, fontSize: this.hotZoneFontObj.size, fontName: this.hotZoneFontObj.name, fontColor: this.hotZoneFontObj.color, fontScale: hotZoneArr[i].textLabel ? hotZoneArr[i].textLabel.scaleX : 1, imgScale: hotZoneArr[i].pic ? hotZoneArr[i].pic.scaleX : 1, mapScale: this.mapScale }; hotZoneItem['rect'] = hotZoneArr[i].getBoundingBox(); hotZoneItem['rect'].x = Math.round((hotZoneItem['rect'].x - bgItem['rect'].x) * 100) / 100; hotZoneItem['rect'].y = Math.round((hotZoneItem['rect'].y - bgItem['rect'].y) * 100) / 100; hotZoneItem['rect'].width = Math.round((hotZoneItem['rect'].width) * 100) / 100; hotZoneItem['rect'].height = Math.round((hotZoneItem['rect'].height) * 100) / 100; hotZoneItemArr.push(hotZoneItem); } console.log('hotZoneItemArr: ', hotZoneItemArr); this.save.emit({bgItem, hotZoneItemArr}); } private updatePos() { this.hotZoneArr.forEach((item) => { let x, y; switch (item.itemType) { case 'rect': x = item.x; y = item.y; break; case 'pic': x = item.pic.x; y = item.pic.y; break; case 'text': x = item.textLabel.x; y = item.textLabel.y; break; } item.x = x; item.y = y; item.pic.x = x; item.pic.y = y; item.textLabel.x = x; item.textLabel.y = y; }); } private setPicState(item: any) { item.visible = false; item.textLabel.visible = false; item.pic.visible = true; } private setRectState(item: any) { item.visible = true; item.textLabel.visible = false; item.pic.visible = false; } private setTextState(item: any) { item.visible = false; item.pic.visible = false; item.textLabel.visible = true; } private clickedHotZoneRect(item: any) { if (this.checkClickTarget(item)) { if (item.lineDashFlag && this.checkClickTarget(item.arrow)) { this.changeItemSize(item); } else if (item.lineDashFlag && this.checkClickTarget(item.arrowTop)) { this.changeItemTopSize(item); } else if (item.lineDashFlag && this.checkClickTarget(item.arrowRight)) { this.changeItemRightSize(item); } else { this.changeCurItem(item); } return; } } private clickedHotZonePic(item: any) { if (this.checkClickTarget(item)) { this.curItem = item; } } private clickedHotZoneText(item: any) { if (this.checkClickTarget(item)) { this.curItem = item; } } saveText(item) { item.textLabel.text = item.text; } private loadHotZonePic(pic: HotZoneImg, url) { const baseLen = this.hotZoneImgSize * this.mapScale; pic.load(url).then(() => { const s = getMinScale(pic, baseLen); pic.setScaleXY(s); }); } }