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 };