From 2d89a6ed9a09f8b7cb09405e1427c1b930b2e316 Mon Sep 17 00:00:00 2001 From: chenjiahan Date: Sun, 28 Jun 2020 17:03:00 +0800 Subject: [PATCH] refactor(ImagePreview): add ImagePreviewItem --- src/image-preview/ImagePreview.js | 260 ++------------------------ src/image-preview/ImagePreviewItem.js | 242 ++++++++++++++++++++++++ src/image-preview/shared.js | 5 + 3 files changed, 261 insertions(+), 246 deletions(-) create mode 100644 src/image-preview/ImagePreviewItem.js create mode 100644 src/image-preview/shared.js diff --git a/src/image-preview/ImagePreview.js b/src/image-preview/ImagePreview.js index 0066d7ed8..24bfcbc89 100644 --- a/src/image-preview/ImagePreview.js +++ b/src/image-preview/ImagePreview.js @@ -1,7 +1,5 @@ // Utils -import { createNamespace } from '../utils'; -import { range } from '../utils/format/number'; -import { on, preventDefault } from '../utils/dom/event'; +import { bem, createComponent } from './shared'; // Mixins import { PopupMixin } from '../mixins/popup'; @@ -9,20 +7,8 @@ import { TouchMixin } from '../mixins/touch'; // Components import Icon from '../icon'; -import Image from '../image'; import Swipe from '../swipe'; -import Loading from '../loading'; -import SwipeItem from '../swipe-item'; - -const [createComponent, bem] = createNamespace('image-preview'); -const DOUBLE_CLICK_INTERVAL = 250; - -function getDistance(touches) { - return Math.sqrt( - (touches[0].clientX - touches[1].clientX) ** 2 + - (touches[0].clientY - touches[1].clientY) ** 2 - ); -} +import ImagePreviewItem from './ImagePreviewItem'; export default createComponent({ mixins: [ @@ -34,6 +20,7 @@ export default createComponent({ props: { className: null, + closeable: Boolean, asyncClose: Boolean, showIndicators: Boolean, images: { @@ -72,7 +59,6 @@ export default createComponent({ type: String, default: bem('overlay'), }, - closeable: Boolean, closeIcon: { type: String, default: 'clear', @@ -84,38 +70,12 @@ export default createComponent({ }, data() { - this.imageSizes = []; - this.windowWidth = window.innerWidth; - this.windowHeight = window.innerHeight; - return { - scale: 1, - moveX: 0, - moveY: 0, active: 0, - moving: false, - zooming: false, doubleClickTimer: null, }; }, - computed: { - imageStyle() { - const { scale } = this; - const style = { - transitionDuration: this.zooming || this.moving ? '0s' : '.3s', - }; - - if (scale !== 1) { - style.transform = `scale(${scale}, ${scale}) translate(${ - this.moveX / scale - }px, ${this.moveY / scale}px)`; - } - - return style; - }, - }, - watch: { startPosition: 'setActive', @@ -132,21 +92,6 @@ export default createComponent({ }); } }, - - shouldRender: { - handler(val) { - if (val) { - this.$nextTick(() => { - const swipe = this.$refs.swipe.$el; - on(swipe, 'touchstart', this.onWrapperTouchStart); - on(swipe, 'touchmove', preventDefault); - on(swipe, 'touchend', this.onWrapperTouchEnd); - on(swipe, 'touchcancel', this.onWrapperTouchEnd); - }); - } - }, - immediate: true, - }, }, methods: { @@ -156,180 +101,17 @@ export default createComponent({ } }, - onWrapperTouchStart() { - this.touchStartTime = new Date(); - }, - - onWrapperTouchEnd(event) { - preventDefault(event); - - const deltaTime = new Date() - this.touchStartTime; - const { offsetX = 0, offsetY = 0 } = this.$refs.swipe || {}; - - // prevent long tap to close component - if (deltaTime < DOUBLE_CLICK_INTERVAL && offsetX < 10 && offsetY < 10) { - if (!this.doubleClickTimer) { - this.doubleClickTimer = setTimeout(() => { - this.emitClose(); - - this.doubleClickTimer = null; - }, DOUBLE_CLICK_INTERVAL); - } else { - clearTimeout(this.doubleClickTimer); - this.doubleClickTimer = null; - this.toggleScale(); - } - } - }, - - startMove(event) { - const image = event.currentTarget; - this.touchStart(event); - this.setMaxMove(image.dataset.index); - this.moving = true; - this.startMoveX = this.moveX; - this.startMoveY = this.moveY; - }, - - setMaxMove(index) { - const { scale, windowWidth, windowHeight } = this; - - if (this.imageSizes[index]) { - const { displayWidth, displayHeight } = this.imageSizes[index]; - this.maxMoveX = Math.max(0, (displayWidth * scale - windowWidth) / 2); - this.maxMoveY = Math.max(0, (displayHeight * scale - windowHeight) / 2); - } else { - this.maxMoveX = 0; - this.maxMoveY = 0; - } - }, - - startZoom(event) { - this.moving = false; - this.zooming = true; - this.startScale = this.scale; - this.startDistance = getDistance(event.touches); - }, - - onImageLoad(event, index) { - const { windowWidth, windowHeight } = this; - const { naturalWidth, naturalHeight } = event.target; - const windowRatio = windowHeight / windowWidth; - const imageRatio = naturalHeight / naturalWidth; - - let displayWidth; - let displayHeight; - - if (imageRatio < windowRatio) { - displayWidth = windowWidth; - displayHeight = windowWidth * imageRatio; - } else { - displayWidth = windowHeight / imageRatio; - displayHeight = windowHeight; - } - - this.imageSizes[index] = { - naturalWidth, - naturalHeight, - displayWidth, - displayHeight, - }; - }, - - onImageTouchStart(event) { - const { touches } = event; - const { offsetX = 0 } = this.$refs.swipe || {}; - - if (touches.length === 1 && this.scale !== 1) { - this.startMove(event); - } /* istanbul ignore else */ else if (touches.length === 2 && !offsetX) { - this.startZoom(event); - } - }, - - onImageTouchMove(event) { - const { touches } = event; - if (this.moving || this.zooming) { - preventDefault(event, true); - } - - if (this.moving) { - this.touchMove(event); - const moveX = this.deltaX + this.startMoveX; - const moveY = this.deltaY + this.startMoveY; - this.moveX = range(moveX, -this.maxMoveX, this.maxMoveX); - this.moveY = range(moveY, -this.maxMoveY, this.maxMoveY); - } - - if (this.zooming && touches.length === 2) { - const distance = getDistance(touches); - const scale = (this.startScale * distance) / this.startDistance; - - this.setScale(scale); - } - }, - - onImageTouchEnd(event) { - /* istanbul ignore else */ - if (this.moving || this.zooming) { - let stopPropagation = true; - - if ( - this.moving && - this.startMoveX === this.moveX && - this.startMoveY === this.moveY - ) { - stopPropagation = false; - } - - if (!event.touches.length) { - this.moving = false; - this.zooming = false; - this.startMoveX = 0; - this.startMoveY = 0; - this.startScale = 1; - - if (this.scale < 1) { - this.resetScale(); - } - } - - if (stopPropagation) { - preventDefault(event, true); - } - } + emitScale(args) { + this.$emit('scale', args); }, setActive(active) { - this.resetScale(); - if (active !== this.active) { this.active = active; this.$emit('change', active); } }, - setScale(scale) { - const value = range(scale, +this.minZoom, +this.maxZoom); - - this.scale = value; - this.$emit('scale', { index: this.active, scale: value }); - }, - - resetScale() { - this.setScale(1); - this.moveX = 0; - this.moveY = 0; - }, - - toggleScale() { - const scale = this.scale > 1 ? 1 : 2; - - this.setScale(scale); - this.moveX = 0; - this.moveY = 0; - }, - genIndex() { if (this.showIndex) { return ( @@ -350,40 +132,26 @@ export default createComponent({ }, genImages() { - const imageSlots = { - loading: () => , - }; - return ( - {this.images.map((image, index) => ( - - { - this.onImageLoad(event, index); - }} - /> - + {this.images.map((image) => ( + ))} ); diff --git a/src/image-preview/ImagePreviewItem.js b/src/image-preview/ImagePreviewItem.js new file mode 100644 index 000000000..143e2b322 --- /dev/null +++ b/src/image-preview/ImagePreviewItem.js @@ -0,0 +1,242 @@ +// Utils +import { bem } from './shared'; +import { range } from '../utils/format/number'; +import { preventDefault } from '../utils/dom/event'; + +// Mixins +import { TouchMixin } from '../mixins/touch'; + +// Component +import Image from '../image'; +import Loading from '../loading'; +import SwipeItem from '../swipe-item'; + +const DOUBLE_CLICK_INTERVAL = 250; + +function getDistance(touches) { + return Math.sqrt( + (touches[0].clientX - touches[1].clientX) ** 2 + + (touches[0].clientY - touches[1].clientY) ** 2 + ); +} + +export default { + mixins: [TouchMixin], + + props: { + src: String, + minZoom: [Number, String], + maxZoom: [Number, String], + }, + + data() { + this.windowWidth = window.innerWidth; + this.windowHeight = window.innerHeight; + + return { + scale: 1, + moveX: 0, + moveY: 0, + moving: false, + zooming: false, + }; + }, + + computed: { + imageStyle() { + const { scale } = this; + const style = { + transitionDuration: this.zooming || this.moving ? '0s' : '.3s', + }; + + if (scale !== 1) { + const offsetX = this.moveX / scale; + const offsetY = this.moveY / scale; + style.transform = `scale(${scale}, ${scale}) translate(${offsetX}px, ${offsetY}px)`; + } + + return style; + }, + }, + + methods: { + startMove(event) { + this.touchStart(event); + this.setMaxMove(); + this.moving = true; + this.startMoveX = this.moveX; + this.startMoveY = this.moveY; + }, + + setMaxMove() { + const { + scale, + windowWidth, + windowHeight, + displayWidth, + displayHeight, + } = this; + + if (this.displayWidth && this.displayHeight) { + this.maxMoveX = Math.max(0, (displayWidth * scale - windowWidth) / 2); + this.maxMoveY = Math.max(0, (displayHeight * scale - windowHeight) / 2); + } + }, + + resetScale() { + this.setScale(1); + this.moveX = 0; + this.moveY = 0; + }, + + setScale(scale) { + this.scale = range(scale, +this.minZoom, +this.maxZoom); + this.$emit('scale', { + scale: this.scale, + index: this.active, + }); + }, + + toggleScale() { + const scale = this.scale > 1 ? 1 : 2; + + this.setScale(scale); + this.moveX = 0; + this.moveY = 0; + }, + + onTouchStart(event) { + const { touches } = event; + const { offsetX = 0 } = this; + + this.touchStartTime = new Date(); + + if (touches.length === 1 && this.scale !== 1) { + this.startMove(event); + } else if (touches.length === 2 && !offsetX) { + this.startZoom(event); + } + }, + + onTouchMove(event) { + const { touches } = event; + + this.touchMove(event); + + if (this.moving || this.zooming) { + preventDefault(event, true); + } + + if (this.moving) { + const moveX = this.deltaX + this.startMoveX; + const moveY = this.deltaY + this.startMoveY; + this.moveX = range(moveX, -this.maxMoveX, this.maxMoveX); + this.moveY = range(moveY, -this.maxMoveY, this.maxMoveY); + } + + if (this.zooming && touches.length === 2) { + const distance = getDistance(touches); + const scale = (this.startScale * distance) / this.startDistance; + + this.setScale(scale); + } + }, + + onTouchEnd(event) { + /* istanbul ignore else */ + if (this.moving || this.zooming) { + let stopPropagation = true; + + if ( + this.moving && + this.startMoveX === this.moveX && + this.startMoveY === this.moveY + ) { + stopPropagation = false; + } + + if (!event.touches.length) { + this.moving = false; + this.zooming = false; + this.startMoveX = 0; + this.startMoveY = 0; + this.startScale = 1; + + if (this.scale < 1) { + this.resetScale(); + } + } + + if (stopPropagation) { + preventDefault(event, true); + } + } + + const deltaTime = new Date() - this.touchStartTime; + const { offsetX = 0, offsetY = 0 } = this; + + // prevent long tap to close component + if (deltaTime < DOUBLE_CLICK_INTERVAL && offsetX < 10 && offsetY < 10) { + if (!this.doubleClickTimer) { + this.doubleClickTimer = setTimeout(() => { + this.$emit('close'); + + this.doubleClickTimer = null; + }, DOUBLE_CLICK_INTERVAL); + } else { + clearTimeout(this.doubleClickTimer); + this.doubleClickTimer = null; + this.toggleScale(); + } + } + + this.resetTouchStatus(); + }, + + startZoom(event) { + this.moving = false; + this.zooming = true; + this.startScale = this.scale; + this.startDistance = getDistance(event.touches); + }, + + onLoad(event) { + const { windowWidth, windowHeight } = this; + const { naturalWidth, naturalHeight } = event.target; + const windowRatio = windowHeight / windowWidth; + const imageRatio = naturalHeight / naturalWidth; + + if (imageRatio < windowRatio) { + this.displayWidth = windowWidth; + this.displayHeight = windowWidth * imageRatio; + } else { + this.displayWidth = windowHeight / imageRatio; + this.displayHeight = windowHeight; + } + }, + }, + + render() { + const imageSlots = { + loading: () => , + }; + + return ( + + + + ); + }, +}; diff --git a/src/image-preview/shared.js b/src/image-preview/shared.js new file mode 100644 index 000000000..c3e8e5ba8 --- /dev/null +++ b/src/image-preview/shared.js @@ -0,0 +1,5 @@ +import { createNamespace } from '../utils'; + +const [createComponent, bem] = createNamespace('image-preview'); + +export { createComponent, bem };