diff --git a/src/composition/use-touch.ts b/src/composition/use-touch.ts index 7d37889d0..ee44276f1 100644 --- a/src/composition/use-touch.ts +++ b/src/composition/use-touch.ts @@ -52,6 +52,7 @@ export function useTouch() { return { move, start, + reset, startX, startY, deltaX, diff --git a/src/image-preview/ImagePreviewItem.js b/src/image-preview/ImagePreviewItem.js index f33f762dd..ab6e9f5dc 100644 --- a/src/image-preview/ImagePreviewItem.js +++ b/src/image-preview/ImagePreviewItem.js @@ -1,10 +1,12 @@ +import { watch, computed, reactive } from 'vue'; + // Utils import { bem } from './shared'; import { range } from '../utils/format/number'; import { preventDefault } from '../utils/dom/event'; -// Mixins -import { TouchMixin } from '../mixins/touch'; +// Composition +import { useTouch } from '../composition/use-touch'; // Component import Image from '../image'; @@ -19,8 +21,6 @@ function getDistance(touches) { } export default { - mixins: [TouchMixin], - props: { src: String, show: Boolean, @@ -33,8 +33,8 @@ export default { emits: ['scale', 'close'], - data() { - return { + setup(props, { emit }) { + const state = reactive({ scale: 1, moveX: 0, moveY: 0, @@ -43,163 +43,183 @@ export default { imageRatio: 0, displayWidth: 0, displayHeight: 0, - }; - }, + }); - computed: { - vertical() { - const { rootWidth, rootHeight } = this; + const touch = useTouch(); + + const vertical = computed(() => { + const { rootWidth, rootHeight } = props; const rootRatio = rootHeight / rootWidth; - return this.imageRatio > rootRatio; - }, + return state.imageRatio > rootRatio; + }); - imageStyle() { - const { scale } = this; + const imageStyle = computed(() => { + const { scale, moveX, moveY, moving, zooming } = state; const style = { - transitionDuration: this.zooming || this.moving ? '0s' : '.3s', + transitionDuration: zooming || moving ? '0s' : '.3s', }; if (scale !== 1) { - const offsetX = this.moveX / scale; - const offsetY = this.moveY / scale; + const offsetX = moveX / scale; + const offsetY = moveY / scale; style.transform = `scale(${scale}, ${scale}) translate(${offsetX}px, ${offsetY}px)`; } return style; - }, + }); - maxMoveX() { - if (this.imageRatio) { - const displayWidth = this.vertical - ? this.rootHeight / this.imageRatio - : this.rootWidth; + const maxMoveX = computed(() => { + if (state.imageRatio) { + const { rootWidth, rootHeight } = props; + const displayWidth = vertical.value + ? rootHeight / state.imageRatio + : rootWidth; - return Math.max(0, (this.scale * displayWidth - this.rootWidth) / 2); + return Math.max(0, (state.scale * displayWidth - rootWidth) / 2); } return 0; - }, + }); - maxMoveY() { - if (this.imageRatio) { - const displayHeight = this.vertical - ? this.rootHeight - : this.rootWidth * this.imageRatio; + const maxMoveY = computed(() => { + if (state.imageRatio) { + const { rootWidth, rootHeight } = props; + const displayHeight = vertical.value + ? rootHeight + : rootWidth * state.imageRatio; - return Math.max(0, (this.scale * displayHeight - this.rootHeight) / 2); + return Math.max(0, (state.scale * displayHeight - rootHeight) / 2); } return 0; - }, - }, + }); - watch: { - show(val) { - if (!val) { - this.resetScale(); - } - }, - }, - - mounted() { - this.bindTouchEvent(this.$el); - }, - - methods: { - 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, + const setScale = (scale) => { + state.scale = range(scale, +props.minZoom, +props.maxZoom); + emit('scale', { + scale: state.scale, + index: state.active, }); - }, + }; - toggleScale() { - const scale = this.scale > 1 ? 1 : 2; + const resetScale = () => { + setScale(1); + state.moveX = 0; + state.moveY = 0; + }; - this.setScale(scale); - this.moveX = 0; - this.moveY = 0; - }, + const toggleScale = () => { + const scale = state.scale > 1 ? 1 : 2; - onTouchStart(event) { + setScale(scale); + state.moveX = 0; + state.moveY = 0; + }; + + let startMoveX; + let startMoveY; + let startScale; + let startDistance; + let doubleTapTimer; + let touchStartTime; + + const onTouchStart = (event) => { const { touches } = event; - const { offsetX = 0 } = this; + const { offsetX } = touch; - this.touchStart(event); - this.touchStartTime = new Date(); + touch.start(event); - this.startMoveX = this.moveX; - this.startMoveY = this.moveY; + startMoveX = state.moveX; + startMoveY = state.moveY; + touchStartTime = new Date(); - this.moving = touches.length === 1 && this.scale !== 1; - this.zooming = touches.length === 2 && !offsetX; + state.moving = touches.length === 1 && state.scale !== 1; + state.zooming = touches.length === 2 && !offsetX.value; - if (this.zooming) { - this.startScale = this.scale; - this.startDistance = getDistance(event.touches); + if (state.zooming) { + startScale = state.scale; + startDistance = getDistance(event.touches); } - }, + }; - onTouchMove(event) { + const onTouchMove = (event) => { const { touches } = event; - this.touchMove(event); + touch.move(event); - if (this.moving || this.zooming) { + if (state.moving || state.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 (state.moving) { + const { deltaX, deltaY } = touch; + const moveX = deltaX.value + startMoveX; + const moveY = deltaY.value + startMoveY; + state.moveX = range(moveX, -maxMoveX.value, maxMoveX.value); + state.moveY = range(moveY, -maxMoveY.value, maxMoveY.value); } - if (this.zooming && touches.length === 2) { + if (state.zooming && touches.length === 2) { const distance = getDistance(touches); - const scale = (this.startScale * distance) / this.startDistance; + const scale = (startScale * distance) / startDistance; - this.setScale(scale); + setScale(scale); } - }, + }; - onTouchEnd(event) { + const checkTap = () => { + const { offsetX, offsetY } = touch; + const deltaTime = new Date() - touchStartTime; + const TAP_TIME = 250; + const TAP_OFFSET = 10; + + if ( + offsetX.value < TAP_OFFSET && + offsetY.value < TAP_OFFSET && + deltaTime < TAP_TIME + ) { + if (doubleTapTimer) { + clearTimeout(doubleTapTimer); + doubleTapTimer = null; + toggleScale(); + } else { + doubleTapTimer = setTimeout(() => { + emit('close'); + doubleTapTimer = null; + }, TAP_TIME); + } + } + }; + + const onTouchEnd = (event) => { let stopPropagation = false; /* istanbul ignore else */ - if (this.moving || this.zooming) { + if (state.moving || state.zooming) { stopPropagation = true; if ( - this.moving && - this.startMoveX === this.moveX && - this.startMoveY === this.moveY + state.moving && + startMoveX === state.moveX && + startMoveY === state.moveY ) { stopPropagation = false; } if (!event.touches.length) { - if (this.zooming) { - this.moveX = range(this.moveX, -this.maxMoveX, this.maxMoveX); - this.moveY = range(this.moveY, -this.maxMoveY, this.maxMoveY); - this.zooming = false; + if (state.zooming) { + state.moveX = range(state.moveX, -maxMoveX.value, maxMoveX.value); + state.moveY = range(state.moveY, -maxMoveY.value, maxMoveY.value); + state.zooming = false; } - this.moving = false; - this.startMoveX = 0; - this.startMoveY = 0; - this.startScale = 1; + state.moving = false; + startMoveX = 0; + startMoveY = 0; + startScale = 1; - if (this.scale < 1) { - this.resetScale(); + if (state.scale < 1) { + resetScale(); } } } @@ -207,56 +227,47 @@ export default { // eliminate tap delay on safari preventDefault(event, stopPropagation); - this.checkTap(); - this.resetTouchStatus(); - }, - - checkTap() { - const { offsetX = 0, offsetY = 0 } = this; - const deltaTime = new Date() - this.touchStartTime; - const TAP_TIME = 250; - const TAP_OFFSET = 10; - - if ( - offsetX < TAP_OFFSET && - offsetY < TAP_OFFSET && - deltaTime < TAP_TIME - ) { - if (this.doubleTapTimer) { - clearTimeout(this.doubleTapTimer); - this.doubleTapTimer = null; - this.toggleScale(); - } else { - this.doubleTapTimer = setTimeout(() => { - this.$emit('close'); - this.doubleTapTimer = null; - }, TAP_TIME); - } - } - }, - - onLoad(event) { - const { naturalWidth, naturalHeight } = event.target; - this.imageRatio = naturalHeight / naturalWidth; - }, - }, - - render() { - const imageSlots = { - loading: () => , + checkTap(); + touch.reset(); }; - return ( - - - + const onLoad = (event) => { + const { naturalWidth, naturalHeight } = event.target; + state.imageRatio = naturalHeight / naturalWidth; + }; + + watch( + () => props.show, + (value) => { + if (!value) { + resetScale(); + } + } ); + + return () => { + const imageSlots = { + loading: () => , + }; + + return ( + + + + ); + }; }, };