[improvement] rename packages dir to src (#3659)
This commit is contained in:
@@ -0,0 +1,305 @@
|
||||
import { createNamespace, isServer } from '../utils';
|
||||
import { range } from '../utils/format/number';
|
||||
import { preventDefault } from '../utils/dom/event';
|
||||
import { PopupMixin } from '../mixins/popup';
|
||||
import { TouchMixin } from '../mixins/touch';
|
||||
import Swipe from '../swipe';
|
||||
import SwipeItem from '../swipe-item';
|
||||
|
||||
const [createComponent, bem] = createNamespace('image-preview');
|
||||
|
||||
function getDistance(touches) {
|
||||
return Math.sqrt(
|
||||
Math.abs(
|
||||
(touches[0].clientX - touches[1].clientX) *
|
||||
(touches[0].clientY - touches[1].clientY)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default createComponent({
|
||||
mixins: [PopupMixin, TouchMixin],
|
||||
|
||||
props: {
|
||||
images: Array,
|
||||
className: null,
|
||||
lazyLoad: Boolean,
|
||||
asyncClose: Boolean,
|
||||
startPosition: Number,
|
||||
showIndicators: Boolean,
|
||||
closeOnPopstate: Boolean,
|
||||
loop: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
overlay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showIndex: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
minZoom: {
|
||||
type: Number,
|
||||
default: 1 / 3
|
||||
},
|
||||
maxZoom: {
|
||||
type: Number,
|
||||
default: 3
|
||||
},
|
||||
overlayClass: {
|
||||
type: String,
|
||||
default: 'van-image-preview__overlay'
|
||||
},
|
||||
closeOnClickOverlay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
this.bindStatus = false;
|
||||
|
||||
return {
|
||||
scale: 1,
|
||||
moveX: 0,
|
||||
moveY: 0,
|
||||
moving: false,
|
||||
zooming: false,
|
||||
active: 0
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
imageStyle() {
|
||||
const { scale } = this;
|
||||
const style = {
|
||||
transition: this.zooming || this.moving ? '' : '.3s all'
|
||||
};
|
||||
|
||||
if (scale !== 1) {
|
||||
style.transform = `scale3d(${scale}, ${scale}, 1) translate(${this.moveX /
|
||||
scale}px, ${this.moveY / scale}px)`;
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value() {
|
||||
this.active = this.startPosition;
|
||||
},
|
||||
|
||||
startPosition(active) {
|
||||
this.active = active;
|
||||
},
|
||||
|
||||
closeOnPopstate: {
|
||||
handler(val) {
|
||||
this.handlePopstate(val);
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handlePopstate(bind) {
|
||||
/* istanbul ignore if */
|
||||
if (isServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.bindStatus !== bind) {
|
||||
this.bindStatus = bind;
|
||||
const action = bind ? 'add' : 'remove';
|
||||
window[`${action}EventListener`]('popstate', this.close);
|
||||
}
|
||||
},
|
||||
|
||||
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 < 300 && offsetX < 10 && offsetY < 10) {
|
||||
const index = this.active;
|
||||
|
||||
this.resetScale();
|
||||
this.$emit('close', {
|
||||
index,
|
||||
url: this.images[index]
|
||||
});
|
||||
|
||||
if (!this.asyncClose) {
|
||||
this.$emit('input', false);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
startMove(event) {
|
||||
const image = event.currentTarget;
|
||||
const rect = image.getBoundingClientRect();
|
||||
const winWidth = window.innerWidth;
|
||||
const winHeight = window.innerHeight;
|
||||
|
||||
this.touchStart(event);
|
||||
this.moving = true;
|
||||
this.startMoveX = this.moveX;
|
||||
this.startMoveY = this.moveY;
|
||||
this.maxMoveX = Math.max(0, (rect.width - winWidth) / 2);
|
||||
this.maxMoveY = Math.max(0, (rect.height - winHeight) / 2);
|
||||
},
|
||||
|
||||
startZoom(event) {
|
||||
this.moving = false;
|
||||
this.zooming = true;
|
||||
this.startScale = this.scale;
|
||||
this.startDistance = getDistance(event.touches);
|
||||
},
|
||||
|
||||
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.scale = range(scale, this.minZoom, this.maxZoom);
|
||||
}
|
||||
},
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onSwipeChange(active) {
|
||||
this.resetScale();
|
||||
this.active = active;
|
||||
this.$emit('change', active);
|
||||
},
|
||||
|
||||
resetScale() {
|
||||
this.scale = 1;
|
||||
this.moveX = 0;
|
||||
this.moveY = 0;
|
||||
}
|
||||
},
|
||||
|
||||
render(h) {
|
||||
if (!this.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { active, images } = this;
|
||||
|
||||
const Index = this.showIndex && (
|
||||
<div class={bem('index')}>
|
||||
{this.slots('index') || `${active + 1}/${images.length}`}
|
||||
</div>
|
||||
);
|
||||
|
||||
const Images = (
|
||||
<Swipe
|
||||
ref="swipe"
|
||||
loop={this.loop}
|
||||
indicatorColor="white"
|
||||
initialSwipe={this.startPosition}
|
||||
showIndicators={this.showIndicators}
|
||||
onChange={this.onSwipeChange}
|
||||
>
|
||||
{images.map((image, index) => {
|
||||
const props = {
|
||||
class: bem('image'),
|
||||
style: index === active ? this.imageStyle : null,
|
||||
on: {
|
||||
touchstart: this.onImageTouchStart,
|
||||
touchmove: this.onImageTouchMove,
|
||||
touchend: this.onImageTouchEnd,
|
||||
touchcancel: this.onImageTouchEnd
|
||||
}
|
||||
};
|
||||
return (
|
||||
<SwipeItem>
|
||||
{this.lazyLoad ? (
|
||||
<img vLazy={image} {...props} />
|
||||
) : (
|
||||
<img src={image} {...props} />
|
||||
)}
|
||||
</SwipeItem>
|
||||
);
|
||||
})}
|
||||
</Swipe>
|
||||
);
|
||||
|
||||
return (
|
||||
<transition name="van-fade">
|
||||
<div
|
||||
class={[bem(), this.className]}
|
||||
onTouchstart={this.onWrapperTouchStart}
|
||||
onTouchMove={preventDefault}
|
||||
onTouchend={this.onWrapperTouchEnd}
|
||||
onTouchcancel={this.onWrapperTouchEnd}
|
||||
>
|
||||
{Index}
|
||||
{Images}
|
||||
</div>
|
||||
</transition>
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<demo-section>
|
||||
<demo-block :title="$t('basicUsage')">
|
||||
<van-button
|
||||
type="primary"
|
||||
@click="showImagePreview"
|
||||
>
|
||||
{{ $t('button1') }}
|
||||
</van-button>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('button2')">
|
||||
<van-button
|
||||
type="primary"
|
||||
@click="showImagePreview(1)"
|
||||
>
|
||||
{{ $t('button2') }}
|
||||
</van-button>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('button3')">
|
||||
<van-button
|
||||
type="primary"
|
||||
@click="showImagePreview(0, 1000)"
|
||||
>
|
||||
{{ $t('button3') }}
|
||||
</van-button>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('componentCall')">
|
||||
<van-button
|
||||
type="primary"
|
||||
@click="componentCall"
|
||||
>
|
||||
{{ $t('componentCall') }}
|
||||
</van-button>
|
||||
<van-image-preview
|
||||
v-model="show"
|
||||
:images="images"
|
||||
@change="onChange"
|
||||
>
|
||||
<template #index>{{ $t('index', index) }}</template>
|
||||
</van-image-preview>
|
||||
</demo-block>
|
||||
</demo-section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ImagePreview } from '../..';
|
||||
|
||||
const images = [
|
||||
'https://img.yzcdn.cn/public_files/2017/09/05/3bd347e44233a868c99cf0fe560232be.jpg',
|
||||
'https://img.yzcdn.cn/public_files/2017/09/05/c0dab461920687911536621b345a0bc9.jpg',
|
||||
'https://img.yzcdn.cn/public_files/2017/09/05/4e3ea0898b1c2c416eec8c11c5360833.jpg',
|
||||
'https://img.yzcdn.cn/public_files/2017/09/05/fd08f07665ed67d50e11b32a21ce0682.jpg'
|
||||
];
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
'zh-CN': {
|
||||
button1: '预览图片',
|
||||
button2: '指定初始位置',
|
||||
button3: '异步关闭',
|
||||
componentCall: '组件调用',
|
||||
index: index => `第${index + 1}页`
|
||||
},
|
||||
'en-US': {
|
||||
button1: 'Show Images',
|
||||
button2: 'Custom Start Position',
|
||||
button3: 'Async Close',
|
||||
componentCall: 'Component Call',
|
||||
index: index => `Page: ${index}`
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
images,
|
||||
index: 0
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
componentCall() {
|
||||
this.show = true;
|
||||
},
|
||||
|
||||
onChange(index) {
|
||||
this.index = index;
|
||||
},
|
||||
|
||||
showImagePreview(position, timer) {
|
||||
const instance = ImagePreview({
|
||||
images,
|
||||
asyncClose: !!timer,
|
||||
closeOnPopstate: true,
|
||||
startPosition: typeof position === 'number' ? position : 0
|
||||
});
|
||||
|
||||
if (timer) {
|
||||
setTimeout(() => {
|
||||
instance.close();
|
||||
}, timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import "../../style/var";
|
||||
|
||||
.demo-image-preview {
|
||||
background-color: @white;
|
||||
|
||||
.van-button {
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.van-image-preview {
|
||||
img {
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,140 @@
|
||||
# ImagePreview
|
||||
|
||||
### Install
|
||||
|
||||
```js
|
||||
import { ImagePreview } from 'vant';
|
||||
|
||||
Vue.use(ImagePreview);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```javascript
|
||||
ImagePreview([
|
||||
'https://img.yzcdn.cn/1.jpg',
|
||||
'https://img.yzcdn.cn/2.jpg'
|
||||
]);
|
||||
```
|
||||
|
||||
### Custom config
|
||||
|
||||
```javascript
|
||||
ImagePreview({
|
||||
images: [
|
||||
'https://img.yzcdn.cn/1.jpg',
|
||||
'https://img.yzcdn.cn/2.jpg'
|
||||
],
|
||||
startPosition: 1,
|
||||
onClose() {
|
||||
// do something
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Async Close
|
||||
|
||||
```javascript
|
||||
const instance = ImagePreview({
|
||||
images: [
|
||||
'https://img.yzcdn.cn/1.jpg',
|
||||
'https://img.yzcdn.cn/2.jpg'
|
||||
],
|
||||
asyncClose: true
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
instance.close();
|
||||
}, 1000);
|
||||
```
|
||||
|
||||
### Component Call
|
||||
|
||||
```html
|
||||
<van-image-preview
|
||||
v-model="show"
|
||||
:images="images"
|
||||
@change="onChange"
|
||||
>
|
||||
<template v-slot:index>Page: { index }</template>
|
||||
</van-image-preview>
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
index: 0,
|
||||
images: [
|
||||
'https://img.yzcdn.cn/1.jpg',
|
||||
'https://img.yzcdn.cn/2.jpg'
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onChange(index) {
|
||||
this.index = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|------|------|------|------|
|
||||
| images | Images URL list | `Array` | `[]` |
|
||||
| startPosition | Start position | `Number` | `0` |
|
||||
| showIndex | Whether to show index | `Boolean` | `true` |
|
||||
| showIndicators | Whether to show indicators | `Boolean` | `false` |
|
||||
| loop | Whether to enable loop | `Boolean` | `true` |
|
||||
| onClose | Triggered when close | `Function` | - |
|
||||
| onChange | Triggered when current image change | `Function` | - |
|
||||
| closeOnPopstate | Whether to close when popstate | `Boolean` | `false` |
|
||||
| asyncClose | Whether to enable async close | `Boolean` | `false` |
|
||||
| className | Custom className | `String | Array | Object` | - |
|
||||
| lazyLoad | Whether to enable thumb lazy load,should register [Lazyload](#/en-US/lazyload) component | `Boolean` | `false` |
|
||||
| maxZoom | Max zoom | `Number` | `3` |
|
||||
| minZoom | Min zoom | `Number` | `1/3` |
|
||||
|
||||
### Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|------|------|------|------|
|
||||
| images | Images URL list | `Array` | `[]` |
|
||||
| start-position | Start position | `Number` | `0` |
|
||||
| show-index | Whether to show index | `Boolean` | `true` |
|
||||
| show-indicators | Whether to show indicators | `Boolean` | `false` |
|
||||
| loop | Whether to enable loop | `Boolean` | `true` |
|
||||
| async-close | Whether to enable async close | `Boolean` | `false` |
|
||||
| close-on-popstate | Whether to close when popstate | `Boolean` | `false` |
|
||||
| class-name | Custom className | `String | Array | Object` | - |
|
||||
| lazy-load | Whether to enable thumb lazy load,should register [Lazyload](#/en-US/lazyload) component | `Boolean` | `false` |
|
||||
| max-zoom | Max zoom | `Number` | `3` |
|
||||
| min-zoom | Min zoom | `Number` | `1/3` |
|
||||
|
||||
### Events
|
||||
|
||||
| Event | Description | Parameters |
|
||||
|------|------|------|
|
||||
| close | Triggered when close | { index, url } |
|
||||
| change | Triggered when current image change | index: index of current image |
|
||||
|
||||
### Slots
|
||||
|
||||
| Name | Description |
|
||||
|------|------|
|
||||
| index | Custom index |
|
||||
|
||||
### onClose Parematers
|
||||
|
||||
| Attribute | Description | Type |
|
||||
|------|------|------|
|
||||
| url | Url of current image | `Number` |
|
||||
| index | Index of current image | `String` |
|
||||
@@ -0,0 +1,64 @@
|
||||
import Vue from 'vue';
|
||||
import VueImagePreview from './ImagePreview';
|
||||
import { isServer } from '../utils';
|
||||
|
||||
let instance;
|
||||
|
||||
const defaultConfig = {
|
||||
images: [],
|
||||
loop: true,
|
||||
value: true,
|
||||
minZoom: 1 / 3,
|
||||
maxZoom: 3,
|
||||
className: '',
|
||||
lazyLoad: false,
|
||||
showIndex: true,
|
||||
asyncClose: false,
|
||||
startPosition: 0,
|
||||
showIndicators: false,
|
||||
closeOnPopstate: false
|
||||
};
|
||||
|
||||
const initInstance = () => {
|
||||
instance = new (Vue.extend(VueImagePreview))({
|
||||
el: document.createElement('div')
|
||||
});
|
||||
document.body.appendChild(instance.$el);
|
||||
|
||||
instance.$on('change', index => {
|
||||
if (instance.onChange) {
|
||||
instance.onChange(index);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const ImagePreview = (images, startPosition = 0) => {
|
||||
/* istanbul ignore if */
|
||||
if (isServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!instance) {
|
||||
initInstance();
|
||||
}
|
||||
|
||||
const options = Array.isArray(images) ? { images, startPosition } : images;
|
||||
|
||||
Object.assign(instance, defaultConfig, options);
|
||||
|
||||
instance.$once('input', show => {
|
||||
instance.value = show;
|
||||
});
|
||||
|
||||
if (options.onClose) {
|
||||
instance.$once('close', options.onClose);
|
||||
}
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
ImagePreview.install = () => {
|
||||
Vue.use(VueImagePreview);
|
||||
};
|
||||
|
||||
export default ImagePreview;
|
||||
@@ -0,0 +1,38 @@
|
||||
@import '../style/var';
|
||||
|
||||
.van-image-preview {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&__image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
&__index {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 50%;
|
||||
color: @image-preview-index-text-color;
|
||||
font-size: @image-preview-index-font-size;
|
||||
letter-spacing: 2px;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
&__overlay {
|
||||
background-color: @image-preview-overlay-background-color;
|
||||
}
|
||||
|
||||
.van-swipe {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`index slot 1`] = `
|
||||
<div class="van-image-preview" name="van-fade">
|
||||
<div class="van-image-preview__index">Custom Index</div>
|
||||
<div class="van-swipe">
|
||||
<div class="van-swipe__track" style="width: 0px; transition-duration: 0ms; transform: translateX(0px);"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`lazy-load prop 1`] = `
|
||||
<div class="van-image-preview" name="van-fade">
|
||||
<div class="van-image-preview__index">1/3</div>
|
||||
<div class="van-swipe">
|
||||
<div class="van-swipe__track" style="width: 0px; transition-duration: 0ms; transform: translateX(0px);">
|
||||
<div class="van-swipe-item" style="width: 0px; height: 100%; transform: translateX(0px);"><img class="van-image-preview__image" style="transition: .3s all;"></div>
|
||||
<div class="van-swipe-item" style="width: 0px; height: 100%; transform: translateX(0px);"><img class="van-image-preview__image"></div>
|
||||
<div class="van-swipe-item" style="width: 0px; height: 100%; transform: translateX(0px);"><img class="van-image-preview__image"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`render image 1`] = `
|
||||
<div class="van-image-preview" name="van-fade">
|
||||
<div class="van-image-preview__index">1/3</div>
|
||||
<div class="van-swipe">
|
||||
<div class="van-swipe__track" style="width: 0px; transition-duration: 500ms; transform: translateX(0px);">
|
||||
<div class="van-swipe-item" style="width: 0px; height: 100%; transform: translateX(0px);"><img src="https://img.yzcdn.cn/1.png" class="van-image-preview__image" style="transition: .3s all;"></div>
|
||||
<div class="van-swipe-item" style="width: 0px; height: 100%; transform: translateX(0px);"><img src="https://img.yzcdn.cn/2.png" class="van-image-preview__image"></div>
|
||||
<div class="van-swipe-item" style="width: 0px; height: 100%; transform: translateX(0px);"><img src="https://img.yzcdn.cn/3.png" class="van-image-preview__image"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`zoom 1`] = `
|
||||
<div class="van-image-preview" name="van-fade">
|
||||
<div class="van-image-preview__index">1/3</div>
|
||||
<div class="van-swipe">
|
||||
<div class="van-swipe__track" style="transition-duration: 500ms; width: 0px; transform: translateX(0px);">
|
||||
<div class="van-swipe-item" style="width: 100px; height: 100%; transform: translateX(0px);"><img src="https://img.yzcdn.cn/1.png" class="van-image-preview__image" style="transform: scale3d(2, 2, 1) translate(0px, NaNpx);"></div>
|
||||
<div class="van-swipe-item" style="width: 100px; height: 100%; transform: translateX(0px);"><img src="https://img.yzcdn.cn/2.png" class="van-image-preview__image"></div>
|
||||
<div class="van-swipe-item" style="width: 100px; height: 100%; transform: translateX(0px);"><img src="https://img.yzcdn.cn/3.png" class="van-image-preview__image"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,168 @@
|
||||
import Vue from 'vue';
|
||||
import ImagePreview from '..';
|
||||
import ImagePreviewVue from '../ImagePreview';
|
||||
import { mount, trigger, triggerDrag, transitionStub, later } from '../../../test/utils';
|
||||
|
||||
transitionStub();
|
||||
|
||||
function triggerZoom(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, 'touchend', 0, 0, { touchList: [] });
|
||||
}
|
||||
|
||||
const images = [
|
||||
'https://img.yzcdn.cn/1.png',
|
||||
'https://img.yzcdn.cn/2.png',
|
||||
'https://img.yzcdn.cn/3.png'
|
||||
];
|
||||
|
||||
test('render image', () => {
|
||||
const wrapper = mount(ImagePreviewVue, {
|
||||
propsData: { images, value: true }
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
const swipe = wrapper.find('.van-swipe__track');
|
||||
triggerDrag(swipe, 500, 0);
|
||||
expect(wrapper.emitted('input')).toBeFalsy();
|
||||
triggerDrag(swipe, 0, 0);
|
||||
expect(wrapper.emitted('input')[0][0]).toBeFalsy();
|
||||
expect(wrapper.emitted('change')[0][0]).toEqual(2);
|
||||
});
|
||||
|
||||
test('async close', () => {
|
||||
const wrapper = mount(ImagePreviewVue, {
|
||||
propsData: {
|
||||
images,
|
||||
value: true,
|
||||
asyncClose: true
|
||||
}
|
||||
});
|
||||
|
||||
const swipe = wrapper.find('.van-swipe__track');
|
||||
triggerDrag(swipe, 0, 0);
|
||||
expect(wrapper.emitted('input')).toBeFalsy();
|
||||
wrapper.vm.close();
|
||||
expect(wrapper.emitted('input')[0][0]).toBeFalsy();
|
||||
});
|
||||
|
||||
test('function call', done => {
|
||||
ImagePreview(images);
|
||||
ImagePreview(images.slice(0, 1));
|
||||
Vue.nextTick(() => {
|
||||
const wrapper = document.querySelector('.van-image-preview');
|
||||
const swipe = wrapper.querySelector('.van-swipe__track');
|
||||
triggerDrag(swipe, 0, 0);
|
||||
|
||||
expect(wrapper.querySelectorAll('img').length).toEqual(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('onClose option', async done => {
|
||||
const onClose = jest.fn();
|
||||
const instance = ImagePreview({
|
||||
images,
|
||||
startPostion: 1,
|
||||
onClose
|
||||
});
|
||||
|
||||
instance.$emit('input', true);
|
||||
expect(onClose).toHaveBeenCalledTimes(0);
|
||||
|
||||
await later();
|
||||
|
||||
const wrapper = document.querySelector('.van-image-preview');
|
||||
const swipe = wrapper.querySelector('.van-swipe__track');
|
||||
triggerDrag(swipe, 0, 0);
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
expect(onClose).toHaveBeenCalledWith({ index: 0, url: 'https://img.yzcdn.cn/1.png' });
|
||||
done();
|
||||
});
|
||||
|
||||
test('onChange option', async done => {
|
||||
const instance = ImagePreview({
|
||||
images,
|
||||
startPostion: 0,
|
||||
onChange(index) {
|
||||
expect(index).toEqual(2);
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
const swipe = instance.$el.querySelector('.van-swipe__track');
|
||||
triggerDrag(swipe, 1000, 0);
|
||||
});
|
||||
|
||||
test('register component', () => {
|
||||
Vue.use(ImagePreview);
|
||||
expect(Vue.component(ImagePreviewVue.name)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('zoom', async () => {
|
||||
const { getBoundingClientRect } = Element.prototype;
|
||||
Element.prototype.getBoundingClientRect = jest.fn(() => ({ width: 100 }));
|
||||
|
||||
const wrapper = mount(ImagePreviewVue, {
|
||||
propsData: { images, value: true }
|
||||
});
|
||||
|
||||
const image = wrapper.find('img');
|
||||
triggerZoom(image, 300, 300);
|
||||
triggerDrag(image, 300, 300);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
Element.prototype.getBoundingClientRect = getBoundingClientRect;
|
||||
});
|
||||
|
||||
test('index slot', () => {
|
||||
const wrapper = mount({
|
||||
template: `
|
||||
<van-image-preview :value="true">
|
||||
<template v-slot:index>Custom Index</template>
|
||||
</van-image-preview>
|
||||
`
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('closeOnPopstate', () => {
|
||||
const wrapper = mount(ImagePreviewVue, {
|
||||
propsData: {
|
||||
images,
|
||||
value: true,
|
||||
closeOnPopstate: true
|
||||
}
|
||||
});
|
||||
|
||||
trigger(window, 'popstate');
|
||||
expect(wrapper.emitted('input')[0][0]).toBeFalsy();
|
||||
|
||||
wrapper.setProps({
|
||||
value: true,
|
||||
closeOnPopstate: false
|
||||
});
|
||||
|
||||
trigger(window, 'popstate');
|
||||
expect(wrapper.emitted('input')[1]).toBeFalsy();
|
||||
});
|
||||
|
||||
test('lazy-load prop', () => {
|
||||
const wrapper = mount(ImagePreviewVue, {
|
||||
propsData: {
|
||||
images,
|
||||
lazyLoad: true
|
||||
}
|
||||
});
|
||||
|
||||
wrapper.setProps({
|
||||
value: true
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,158 @@
|
||||
# ImagePreview 图片预览
|
||||
|
||||
### 引入
|
||||
|
||||
`ImagePreview`和其他组件不同,不是通过HTML结构的方式来使用,而是通过函数调用的方式。使用前需要先引入它。
|
||||
|
||||
```js
|
||||
import { ImagePreview } from 'vant';
|
||||
|
||||
Vue.use(ImagePreview);
|
||||
```
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基础用法
|
||||
|
||||
直接传入图片数组,即可展示图片预览
|
||||
|
||||
```javascript
|
||||
ImagePreview([
|
||||
'https://img.yzcdn.cn/1.jpg',
|
||||
'https://img.yzcdn.cn/2.jpg'
|
||||
]);
|
||||
```
|
||||
|
||||
### 传入配置项
|
||||
|
||||
通过传入配置对象,可以指定初始图片的位置、监听关闭事件
|
||||
|
||||
```javascript
|
||||
ImagePreview({
|
||||
images: [
|
||||
'https://img.yzcdn.cn/1.jpg',
|
||||
'https://img.yzcdn.cn/2.jpg'
|
||||
],
|
||||
startPosition: 1,
|
||||
onClose() {
|
||||
// do something
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 异步关闭
|
||||
|
||||
通过`asyncClose`属性可以开启异步关闭,开启后异步关闭后,只能通过实例上的 close 方法关闭图片预览
|
||||
|
||||
```javascript
|
||||
const instance = ImagePreview({
|
||||
images: [
|
||||
'https://img.yzcdn.cn/1.jpg',
|
||||
'https://img.yzcdn.cn/2.jpg'
|
||||
],
|
||||
asyncClose: true
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
instance.close();
|
||||
}, 1000);
|
||||
```
|
||||
|
||||
### 组件调用
|
||||
|
||||
如果需要在图片预览内嵌入组件或其他自定义内容,可以使用组件调用的方式,调用前需要通过 `Vue.use` 注册组件
|
||||
|
||||
```html
|
||||
<van-image-preview
|
||||
v-model="show"
|
||||
:images="images"
|
||||
@change="onChange"
|
||||
>
|
||||
<template v-slot:index>第{ index }页</template>
|
||||
</van-image-preview>
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
index: 0,
|
||||
images: [
|
||||
'https://img.yzcdn.cn/1.jpg',
|
||||
'https://img.yzcdn.cn/2.jpg'
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onChange(index) {
|
||||
this.index = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
通过函数调用 `ImagePreview` 时,支持传入以下选项:
|
||||
|
||||
| 参数名 | 说明 | 类型 | 默认值 | 版本 |
|
||||
|------|------|------|------|------|
|
||||
| images | 需要预览的图片 URL 数组 | `Array` | `[]` | 1.1.16 |
|
||||
| startPosition | 图片预览起始位置索引 | `Number` | `0` | 1.1.16 |
|
||||
| showIndex | 是否显示页码 | `Boolean` | `true` | 1.3.4 |
|
||||
| showIndicators | 是否显示轮播指示器 | `Boolean` | `false` | 1.3.10 |
|
||||
| loop | 是否开启循环播放 | `Boolean` | `true` | 1.4.4 |
|
||||
| onClose | 关闭时的回调函数 | `Function` | - | 1.1.16 |
|
||||
| onChange | 切换图片时的回调函数,回调参数为当前索引 | `Function` | - | 2.0.3 |
|
||||
| asyncClose | 是否开启异步关闭 | `Boolean` | `false` | 1.4.8 |
|
||||
| closeOnPopstate | 是否在页面回退时自动关闭 | `Boolean` | `false` | 2.0.0 |
|
||||
| className | 自定义类名 | `String | Array | Object` | - | 1.5.2 |
|
||||
| lazyLoad | 是否开启图片懒加载,须配合 [Lazyload](#/zh-CN/lazyload) 组件使用 | `Boolean` | `false` | 1.5.3 |
|
||||
| maxZoom | 手势缩放时,最大缩放比例 | `Number` | `3` | 1.6.14 |
|
||||
| minZoom | 手势缩放时,最小缩放比例 | `Number` | `1/3` | 1.6.14 |
|
||||
|
||||
### Props
|
||||
|
||||
通过组件调用 `ImagePreview` 时,支持以下 Props:
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
|------|------|------|------|------|
|
||||
| images | 需要预览的图片 URL 数组 | `Array` | `[]` | 1.1.16 |
|
||||
| start-position | 图片预览起始位置索引 | `Number` | `0` | 1.1.16 |
|
||||
| show-index | 是否显示页码 | `Boolean` | `true` | 1.3.4 |
|
||||
| show-indicators | 是否显示轮播指示器 | `Boolean` | `false` | 1.3.10 |
|
||||
| loop | 是否开启循环播放 | `Boolean` | `true` | 1.4.4 |
|
||||
| async-close | 是否开启异步关闭 | `Boolean` | `false` | 1.4.8 |
|
||||
| close-on-popstate | 是否在页面回退时自动关闭 | `Boolean` | `false` |
|
||||
| class-name | 自定义类名 | `String | Array | Object` | - | 1.5.2 |
|
||||
| lazy-load | 是否开启图片懒加载,须配合 [Lazyload](#/zh-CN/lazyload) 组件使用 | `Boolean` | `false` | 1.5.3 |
|
||||
| max-zoom | 手势缩放时,最大缩放比例 | `Number` | `3` | 1.6.14 |
|
||||
| min-zoom | 手势缩放时,最小缩放比例 | `Number` | `1/3` | 1.6.14 |
|
||||
|
||||
### Events
|
||||
|
||||
通过组件调用 `ImagePreview` 时,支持以下事件:
|
||||
|
||||
| 事件 | 说明 | 回调参数 |
|
||||
|------|------|------|
|
||||
| close | 关闭时触发 | { index: 索引, url: 图片链接 } |
|
||||
| change | 切换当前图片时触发 | index, 当前图片的索引 |
|
||||
|
||||
### Slots
|
||||
|
||||
通过组件调用 `ImagePreview` 时,支持以下插槽:
|
||||
|
||||
| 名称 | 说明 |
|
||||
|------|------|
|
||||
| index | 自定义页码内容 |
|
||||
|
||||
### onClose 回调参数
|
||||
|
||||
| 参数名 | 说明 | 类型 |
|
||||
|------|------|------|
|
||||
| url | 当前图片 URL | `String` |
|
||||
| index | 当前图片的索引值 | `Number` |
|
||||
Reference in New Issue
Block a user