mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-06 03:57:59 +08:00
Merge pull request #6656 from chenjiahan/image_preview_scale_0630
fix(ImagePreview): incorrect position after scale
This commit is contained in:
parent
e93bec677e
commit
1ee44f772a
@ -11,8 +11,6 @@ import Image from '../image';
|
|||||||
import Loading from '../loading';
|
import Loading from '../loading';
|
||||||
import SwipeItem from '../swipe-item';
|
import SwipeItem from '../swipe-item';
|
||||||
|
|
||||||
const DOUBLE_CLICK_INTERVAL = 250;
|
|
||||||
|
|
||||||
function getDistance(touches) {
|
function getDistance(touches) {
|
||||||
return Math.sqrt(
|
return Math.sqrt(
|
||||||
(touches[0].clientX - touches[1].clientX) ** 2 +
|
(touches[0].clientX - touches[1].clientX) ** 2 +
|
||||||
@ -40,6 +38,8 @@ export default {
|
|||||||
moveY: 0,
|
moveY: 0,
|
||||||
moving: false,
|
moving: false,
|
||||||
zooming: false,
|
zooming: false,
|
||||||
|
displayWidth: 0,
|
||||||
|
displayHeight: 0,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -58,34 +58,33 @@ export default {
|
|||||||
|
|
||||||
return style;
|
return style;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
maxMoveX() {
|
||||||
|
if (this.displayWidth) {
|
||||||
|
return Math.max(
|
||||||
|
0,
|
||||||
|
(this.scale * this.displayWidth - this.windowWidth) / 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
maxMoveY() {
|
||||||
|
if (this.displayHeight) {
|
||||||
|
return Math.max(
|
||||||
|
0,
|
||||||
|
(this.scale * this.displayHeight - this.windowHeight) / 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.bindTouchEvent(this.$el);
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
startMove() {
|
|
||||||
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);
|
|
||||||
} else {
|
|
||||||
this.maxMoveX = 0;
|
|
||||||
this.maxMoveY = 0;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
resetScale() {
|
resetScale() {
|
||||||
this.setScale(1);
|
this.setScale(1);
|
||||||
this.moveX = 0;
|
this.moveX = 0;
|
||||||
@ -115,10 +114,15 @@ export default {
|
|||||||
this.touchStart(event);
|
this.touchStart(event);
|
||||||
this.touchStartTime = new Date();
|
this.touchStartTime = new Date();
|
||||||
|
|
||||||
if (touches.length === 1 && this.scale !== 1) {
|
this.startMoveX = this.moveX;
|
||||||
this.startMove();
|
this.startMoveY = this.moveY;
|
||||||
} else if (touches.length === 2 && !offsetX) {
|
|
||||||
this.startZoom(event);
|
this.moving = touches.length === 1 && this.scale !== 1;
|
||||||
|
this.zooming = touches.length === 2 && !offsetX;
|
||||||
|
|
||||||
|
if (this.zooming) {
|
||||||
|
this.startScale = this.scale;
|
||||||
|
this.startDistance = getDistance(event.touches);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -147,9 +151,12 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onTouchEnd(event) {
|
onTouchEnd(event) {
|
||||||
|
let stopPropagation = false;
|
||||||
|
|
||||||
/* istanbul ignore else */
|
/* istanbul ignore else */
|
||||||
if (this.moving || this.zooming) {
|
if (this.moving || this.zooming) {
|
||||||
let stopPropagation = true;
|
|
||||||
|
stopPropagation = true;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.moving &&
|
this.moving &&
|
||||||
@ -160,8 +167,13 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!event.touches.length) {
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
this.moving = false;
|
this.moving = false;
|
||||||
this.zooming = false;
|
|
||||||
this.startMoveX = 0;
|
this.startMoveX = 0;
|
||||||
this.startMoveY = 0;
|
this.startMoveY = 0;
|
||||||
this.startScale = 1;
|
this.startScale = 1;
|
||||||
@ -170,38 +182,36 @@ export default {
|
|||||||
this.resetScale();
|
this.resetScale();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stopPropagation) {
|
|
||||||
preventDefault(event, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const deltaTime = new Date() - this.touchStartTime;
|
preventDefault(event, stopPropagation);
|
||||||
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.checkTap();
|
||||||
this.resetTouchStatus();
|
this.resetTouchStatus();
|
||||||
},
|
},
|
||||||
|
|
||||||
startZoom(event) {
|
checkTap() {
|
||||||
this.moving = false;
|
const { offsetX = 0, offsetY = 0 } = this;
|
||||||
this.zooming = true;
|
const deltaTime = new Date() - this.touchStartTime;
|
||||||
this.startScale = this.scale;
|
const TAP_TIME = 250;
|
||||||
this.startDistance = getDistance(event.touches);
|
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) {
|
onLoad(event) {
|
||||||
@ -226,12 +236,7 @@ export default {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SwipeItem
|
<SwipeItem>
|
||||||
nativeOnTouchstart={this.onTouchStart}
|
|
||||||
nativeOnTouchmove={this.onTouchMove}
|
|
||||||
nativeOnTouchend={this.onTouchEnd}
|
|
||||||
nativeOnTouchcancel={this.onTouchEnd}
|
|
||||||
>
|
|
||||||
<Image
|
<Image
|
||||||
src={this.src}
|
src={this.src}
|
||||||
fit="contain"
|
fit="contain"
|
||||||
|
@ -60,10 +60,12 @@ exports[`set show-index prop to false 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`zoom 1`] = `
|
exports[`zoom in and drag image to move 1`] = `
|
||||||
<div class="van-image van-image-preview__image" style="transition-duration: 0s; transform: scale(2, 2) translate(0px, 0px);"><img src="https://img.yzcdn.cn/1.png" class="van-image__img" style="object-fit: contain;">
|
<div class="van-image van-image-preview__image" style="transition-duration: 0s; transform: scale(2, 2) translate(0px, 0px);"><img src="https://img.yzcdn.cn/1.png" class="van-image__img" style="object-fit: contain;">
|
||||||
<div class="van-image__loading">
|
<div class="van-image__loading">
|
||||||
<div class="van-loading van-loading--spinner"><span class="van-loading__spinner van-loading__spinner--spinner"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span></div>
|
<div class="van-loading van-loading--spinner"><span class="van-loading__spinner van-loading__spinner--spinner"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`zoom in and drag image to move 2`] = `<div class="van-image van-image-preview__image" style="transition-duration: 0s; transform: scale(2, 2) translate(25px, 25px);"><img src="https://img.yzcdn.cn/1.png" class="van-image__img" style="object-fit: contain;"></div>`;
|
||||||
|
@ -1,14 +1,27 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import ImagePreview from '..';
|
import ImagePreview from '..';
|
||||||
import ImagePreviewVue from '../ImagePreview';
|
import ImagePreviewVue from '../ImagePreview';
|
||||||
import { mount, trigger, triggerDrag, later } from '../../../test';
|
import { mount, trigger, triggerDrag, later, mockGetBoundingClientRect } from '../../../test';
|
||||||
|
|
||||||
function triggerZoom(el, x, y) {
|
function triggerTwoFingerTouchmove(el, x, y) {
|
||||||
trigger(el, 'touchstart', 0, 0, { x, y });
|
|
||||||
trigger(el, 'touchmove', -x / 4, -y / 4, { x, y });
|
|
||||||
trigger(el, 'touchmove', -x / 3, -y / 3, { x, y });
|
|
||||||
trigger(el, 'touchmove', -x / 2, -y / 2, { x, y });
|
|
||||||
trigger(el, 'touchmove', -x, -y, { x, y });
|
trigger(el, 'touchmove', -x, -y, { x, y });
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerZoom(el, x, y, direction = 'in') {
|
||||||
|
trigger(el, 'touchstart', 0, 0, { x, y });
|
||||||
|
|
||||||
|
if (direction === 'in') {
|
||||||
|
triggerTwoFingerTouchmove(el, x / 4, y / 4);
|
||||||
|
triggerTwoFingerTouchmove(el, x / 3, y / 3);
|
||||||
|
triggerTwoFingerTouchmove(el, x / 2, y / 2);
|
||||||
|
triggerTwoFingerTouchmove(el, x, y);
|
||||||
|
} else if (direction === 'out') {
|
||||||
|
triggerTwoFingerTouchmove(el, x, y);
|
||||||
|
triggerTwoFingerTouchmove(el, x / 2, y / 2);
|
||||||
|
triggerTwoFingerTouchmove(el, x / 3, y / 3);
|
||||||
|
triggerTwoFingerTouchmove(el, x / 4, y / 4);
|
||||||
|
}
|
||||||
|
|
||||||
trigger(el, 'touchend', 0, 0, { touchList: [] });
|
trigger(el, 'touchend', 0, 0, { touchList: [] });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,9 +185,7 @@ test('onChange option', async (done) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('onScale option', async (done) => {
|
test('onScale option', async (done) => {
|
||||||
const { getBoundingClientRect } = Element.prototype;
|
const restore = mockGetBoundingClientRect({ width: 100 });
|
||||||
Element.prototype.getBoundingClientRect = jest.fn(() => ({ width: 100 }));
|
|
||||||
|
|
||||||
const instance = ImagePreview({
|
const instance = ImagePreview({
|
||||||
images,
|
images,
|
||||||
startPosition: 0,
|
startPosition: 0,
|
||||||
@ -188,7 +199,7 @@ test('onScale option', async (done) => {
|
|||||||
await later();
|
await later();
|
||||||
const image = instance.$el.querySelector('img');
|
const image = instance.$el.querySelector('img');
|
||||||
triggerZoom(image, 300, 300);
|
triggerZoom(image, 300, 300);
|
||||||
Element.prototype.getBoundingClientRect = getBoundingClientRect;
|
restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('register component', () => {
|
test('register component', () => {
|
||||||
@ -196,21 +207,59 @@ test('register component', () => {
|
|||||||
expect(Vue.component(ImagePreviewVue.name)).toBeTruthy();
|
expect(Vue.component(ImagePreviewVue.name)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('zoom', async () => {
|
test('zoom in and drag image to move', async () => {
|
||||||
const { getBoundingClientRect } = Element.prototype;
|
const restore = mockGetBoundingClientRect({ width: 100 });
|
||||||
Element.prototype.getBoundingClientRect = jest.fn(() => ({ width: 100 }));
|
const originWindowWidth = window.innerWidth;
|
||||||
|
const originWindowHeight = window.innerHeight;
|
||||||
|
|
||||||
|
window.innerWidth = 100;
|
||||||
|
window.innerHeight = 100;
|
||||||
|
|
||||||
const wrapper = mount(ImagePreviewVue, {
|
const wrapper = mount(ImagePreviewVue, {
|
||||||
propsData: { images, value: true },
|
propsData: { images, value: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
await later();
|
await later();
|
||||||
const image = wrapper.find('.van-image');
|
const image = wrapper.find('img');
|
||||||
triggerZoom(image, 300, 300);
|
triggerZoom(image, 300, 300);
|
||||||
triggerDrag(image, 300, 300);
|
|
||||||
|
|
||||||
expect(image).toMatchSnapshot();
|
// mock image size
|
||||||
Element.prototype.getBoundingClientRect = getBoundingClientRect;
|
['naturalWidth', 'naturalHeight'].forEach((key) => {
|
||||||
|
Object.defineProperty(image.element, key, { value: 300 });
|
||||||
|
});
|
||||||
|
|
||||||
|
// drag image before load
|
||||||
|
triggerDrag(image, 300, 300);
|
||||||
|
expect(wrapper.find('.van-image')).toMatchSnapshot();
|
||||||
|
|
||||||
|
// drag image after load
|
||||||
|
image.trigger('load');
|
||||||
|
triggerDrag(image, 300, 300);
|
||||||
|
expect(wrapper.find('.van-image')).toMatchSnapshot();
|
||||||
|
|
||||||
|
window.innerWidth = originWindowWidth;
|
||||||
|
window.innerHeight = originWindowHeight;
|
||||||
|
restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('zoom out', async () => {
|
||||||
|
const restore = mockGetBoundingClientRect({ width: 100 });
|
||||||
|
|
||||||
|
const onScale = jest.fn();
|
||||||
|
const wrapper = mount(ImagePreviewVue, {
|
||||||
|
propsData: { images, value: true },
|
||||||
|
listeners: {
|
||||||
|
scale: onScale,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await later();
|
||||||
|
const image = wrapper.find('.van-image');
|
||||||
|
triggerZoom(image, 300, 300, 'out');
|
||||||
|
|
||||||
|
expect(onScale).toHaveBeenLastCalledWith({ index: 0, scale: 1 });
|
||||||
|
|
||||||
|
restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('set show-index prop to false', () => {
|
test('set show-index prop to false', () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user