From ddf732dbcb2f474308924cb1ed68dbdd5d0dce81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E4=BA=91=E9=A3=9E?= Date: Mon, 3 Jan 2022 17:25:35 +0800 Subject: [PATCH] feat:image previewer --- src/editor/assets/css/index.css | 90 +++++++++++++ src/editor/assets/images/close.svg | 1 + src/editor/assets/images/original-size.svg | 1 + src/editor/assets/images/rotate.svg | 1 + src/editor/assets/images/zoom-in.svg | 1 + src/editor/assets/images/zoom-out.svg | 1 + .../core/draw/particle/ImageParticle.ts | 126 +++++++++++++++++- 7 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 src/editor/assets/images/close.svg create mode 100644 src/editor/assets/images/original-size.svg create mode 100644 src/editor/assets/images/rotate.svg create mode 100644 src/editor/assets/images/zoom-in.svg create mode 100644 src/editor/assets/images/zoom-out.svg diff --git a/src/editor/assets/css/index.css b/src/editor/assets/css/index.css index b0283d8..165b518 100644 --- a/src/editor/assets/css/index.css +++ b/src/editor/assets/css/index.css @@ -109,6 +109,96 @@ opacity: 0.5; } +.image-previewer { + position: fixed; + left: 0; + top: 0; + z-index: 1000; + width: 100%; + height: 100%; + overflow: hidden; + background: #f2f4f7; + display: flex; + align-items: center; + justify-content: center; + animation: previewerAnimation .3s; +} + +@keyframes previewerAnimation { + 0% { + opacity: 0.1; + } + + 100% { + opacity: 1; + } +} + +.image-previewer .image-close { + width: 24px; + height: 24px; + display: inline-block; + position: absolute; + right: 50px; + top: 30px; + z-index: 99; + cursor: pointer; + background: url(../images/close.svg) no-repeat; + background-size: 100% 100%; + transition: all .3s; + border-radius: 50%; +} + +.image-previewer .image-close:hover { + background-color: #e2e6ed; +} + +.image-previewer img { + cursor: move; +} + +.image-previewer .image-menu { + height: 50px; + position: absolute; + bottom: 50px; + z-index: 99; + display: flex; + align-items: center; + justify-content: center; +} + +.image-previewer .image-menu i { + width: 32px; + height: 32px; + margin-right: 15px; + cursor: pointer; + display: inline-block; + background-repeat: no-repeat; + background-size: 100% 100%; + transition: all .3s; + border-radius: 50%; +} + +.image-previewer .image-menu i:hover { + background-color: #e2e6ed; +} + +.image-previewer .image-menu i.zoom-in { + background-image: url(../images/zoom-in.svg); +} + +.image-previewer .image-menu i.zoom-out { + background-image: url(../images/zoom-out.svg); +} + +.image-previewer .image-menu i.rotate { + background-image: url(../images/rotate.svg); +} + +.image-previewer .image-menu i.original-size { + background-image: url(../images/original-size.svg); +} + .table-tool__row { position: absolute; width: 12px; diff --git a/src/editor/assets/images/close.svg b/src/editor/assets/images/close.svg new file mode 100644 index 0000000..45aaf1f --- /dev/null +++ b/src/editor/assets/images/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/editor/assets/images/original-size.svg b/src/editor/assets/images/original-size.svg new file mode 100644 index 0000000..38c33ef --- /dev/null +++ b/src/editor/assets/images/original-size.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/editor/assets/images/rotate.svg b/src/editor/assets/images/rotate.svg new file mode 100644 index 0000000..fcc5239 --- /dev/null +++ b/src/editor/assets/images/rotate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/editor/assets/images/zoom-in.svg b/src/editor/assets/images/zoom-in.svg new file mode 100644 index 0000000..c26ff88 --- /dev/null +++ b/src/editor/assets/images/zoom-in.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/editor/assets/images/zoom-out.svg b/src/editor/assets/images/zoom-out.svg new file mode 100644 index 0000000..e828778 --- /dev/null +++ b/src/editor/assets/images/zoom-out.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/editor/core/draw/particle/ImageParticle.ts b/src/editor/core/draw/particle/ImageParticle.ts index 3225684..ba8fb20 100644 --- a/src/editor/core/draw/particle/ImageParticle.ts +++ b/src/editor/core/draw/particle/ImageParticle.ts @@ -12,6 +12,7 @@ export class ImageParticle { private curElement: IElement | null private curPosition: IElementPosition | null private imageCache: Map + // 拖拽改变尺寸 private resizerSelection: HTMLDivElement private resizerHandleList: HTMLDivElement[] private resizerImageContainer: HTMLDivElement @@ -21,6 +22,9 @@ export class ImageParticle { private mousedownX: number private mousedownY: number private curHandleIndex: number + // 预览选区 + private previewerContainer: HTMLDivElement | null + private previewerImage: HTMLImageElement | null constructor(draw: Draw) { this.container = draw.getContainer() @@ -30,6 +34,7 @@ export class ImageParticle { this.curElement = null this.curPosition = null this.imageCache = new Map() + // 图片尺寸缩放 const { resizerSelection, resizerHandleList, resizerImageContainer, resizerImage } = this._createResizerDom() this.resizerSelection = resizerSelection this.resizerHandleList = resizerHandleList @@ -40,6 +45,10 @@ export class ImageParticle { this.mousedownX = 0 this.mousedownY = 0 this.curHandleIndex = 0 // 默认右下角 + // 图片预览 + resizerSelection.ondblclick = this._dblclick.bind(this) + this.previewerContainer = null + this.previewerImage = null } private _createResizerDom(): IImageParticleCreateResult { @@ -54,7 +63,7 @@ export class ImageParticle { handleDom.style.background = this.options.resizerColor handleDom.classList.add(`handle-${i}`) handleDom.setAttribute('data-index', String(i)) - handleDom.onmousedown = this._handleMousedown.bind(this) + handleDom.onmousedown = this._mousedown.bind(this) resizerSelection.append(handleDom) resizerHandleList.push(handleDom) } @@ -69,7 +78,7 @@ export class ImageParticle { return { resizerSelection, resizerHandleList, resizerImageContainer, resizerImage } } - private _handleMousedown(evt: MouseEvent) { + private _mousedown(evt: MouseEvent) { this.canvas = this.draw.getPage() if (!this.curPosition || !this.curElement) return const { scale } = this.options @@ -156,6 +165,119 @@ export class ImageParticle { evt.preventDefault() } + private _dblclick() { + this._drawPreviewer() + document.body.style.overflow = 'hidden' + } + + private _drawPreviewer() { + const previewerContainer = document.createElement('div') + previewerContainer.classList.add('image-previewer') + // 关闭按钮 + const closeBtn = document.createElement('i') + closeBtn.classList.add('image-close') + closeBtn.onclick = () => { + this._clearPreviewer() + } + previewerContainer.append(closeBtn) + // 图片 + const imgContainer = document.createElement('div') + imgContainer.classList.add('image-container') + const img = document.createElement('img') + img.src = this.curElement?.value || '' + img.draggable = false + imgContainer.append(img) + this.previewerImage = img + previewerContainer.append(imgContainer) + // 操作栏 + let scaleSize = 1 + let rotateSize = 0 + let translateX = 0 + let translateY = 0 + const menuContainer = document.createElement('div') + menuContainer.classList.add('image-menu') + const zoomIn = document.createElement('i') + zoomIn.classList.add('zoom-in') + zoomIn.onclick = () => { + scaleSize += 0.1 + this._setPreviewerTransform(scaleSize, rotateSize, translateX, translateY) + } + menuContainer.append(zoomIn) + const zoomOut = document.createElement('i') + zoomOut.onclick = () => { + if (scaleSize - 0.1 <= 0.1) return + scaleSize -= 0.1 + this._setPreviewerTransform(scaleSize, rotateSize, translateX, translateY) + } + zoomOut.classList.add('zoom-out') + menuContainer.append(zoomOut) + const rotate = document.createElement('i') + rotate.classList.add('rotate') + rotate.onclick = () => { + rotateSize += 1 + this._setPreviewerTransform(scaleSize, rotateSize, translateX, translateY) + } + menuContainer.append(rotate) + const originalSize = document.createElement('i') + originalSize.classList.add('original-size') + originalSize.onclick = () => { + scaleSize = 1 + rotateSize = 0 + translateX = 0 + translateY = 0 + this._setPreviewerTransform(scaleSize, rotateSize, translateX, translateY) + } + menuContainer.append(originalSize) + previewerContainer.append(menuContainer) + this.previewerContainer = previewerContainer + document.body.append(previewerContainer) + // 拖拽调整位置 + let startX = 0 + let startY = 0 + let isAllowDrag = false + img.onmousedown = (evt) => { + isAllowDrag = true + startX = evt.x + startY = evt.y + previewerContainer.style.cursor = 'move' + } + previewerContainer.onmousemove = (evt: MouseEvent) => { + if (!isAllowDrag) return + translateX += (evt.x - startX) + translateY += (evt.y - startY) + startX = evt.x + startY = evt.y + this._setPreviewerTransform(scaleSize, rotateSize, translateX, translateY) + } + previewerContainer.onmouseup = () => { + isAllowDrag = false + previewerContainer.style.cursor = 'auto' + } + previewerContainer.onwheel = (evt) => { + evt.preventDefault() + if (evt.deltaY < 0) { + // 放大 + scaleSize += 0.1 + } else { + // 缩小 + if (scaleSize - 0.1 <= 0.1) return + scaleSize -= 0.1 + } + this._setPreviewerTransform(scaleSize, rotateSize, translateX, translateY) + } + } + + public _setPreviewerTransform(scale: number, rotate: number, x: number, y: number) { + if (!this.previewerImage) return + this.previewerImage.style.transform = `scale(${scale}) rotate(${rotate * 90}deg) translate(${x}px,${y}px)` + } + + private _clearPreviewer() { + this.previewerContainer?.remove() + this.previewerContainer = null + document.body.style.overflow = 'auto' + } + public getImageCache(): Map { return this.imageCache }