mirror of
https://github.com/element-plus/element-plus.git
synced 2024-11-27 02:01:15 +08:00
refactor(components): [image, image-viewer] refactor (#6704)
Co-authored-by: 三咲智子 <sxzz@sxzz.moe>
This commit is contained in:
parent
7a4fc4cefb
commit
8c26036e60
@ -47,50 +47,57 @@ image/image-preview
|
||||
|
||||
:::
|
||||
|
||||
## Image Attributes
|
||||
## Image API
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default |
|
||||
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- | ------------------------------------------ | ---------------------------------------------------------------------- |
|
||||
| alt | Native alt | string | - | - |
|
||||
| fit | Indicate how the image should be resized to fit its container, same as [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) | string | fill / contain / cover / none / scale-down | - |
|
||||
| hide-on-click-modal | When enabling preview, use this flag to control whether clicking on backdrop can exit preview mode | boolean | true / false | false |
|
||||
| initial-index | The initial preview image index, less than the length of `url-list` | number | int | 0 |
|
||||
| lazy | Whether to use lazy load | boolean | — | false |
|
||||
| preview-src-list | allow big image preview | Array | — | - |
|
||||
| referrer-policy | Native referrerPolicy | string | - | - |
|
||||
| src | Image source, same as native | string | — | - |
|
||||
| scroll-container | The container to add scroll listener when using lazy load | string / HTMLElement | — | The nearest parent container whose overflow property is auto or scroll |
|
||||
| z-index | set image preview z-index | Number | — | 2000 |
|
||||
| preview-teleported | whether to append image-viewer to body. A nested parent element attribute transform should have this attribute set to `true` | boolean | — | false |
|
||||
### Image Attributes
|
||||
|
||||
## Image Events
|
||||
| Name | Description | Type | Default |
|
||||
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------- |
|
||||
| `src` | image source, same as native. | `string` | — |
|
||||
| `fit` | indicate how the image should be resized to fit its container, same as [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit). | `'fill' \| 'contain' \| 'cover' \| 'none' \| 'scale'-down'` | — |
|
||||
| `hide-on-click-modal` | when enabling preview, use this flag to control whether clicking on backdrop can exit preview mode. | `boolean` | `false` |
|
||||
| `lazy` | whether to use lazy load. | `boolean` | `false` |
|
||||
| `scroll-container` | the container to add scroll listener when using lazy load. | `string \| HTMLElement` | the nearest parent container whose overflow property is auto or scroll. |
|
||||
| `alt` | native attribute `alt`. | `string` | — |
|
||||
| `referrer-policy` | native attribute `referrerPolicy`. | `string` | — |
|
||||
| `preview-src-list` | allow big image preview. | `string[]` | — |
|
||||
| `z-index` | set image preview z-index. | `number` | — |
|
||||
| `initial-index` | initial preview image index, less than the length of `url-list`. | `number` | `0` |
|
||||
| `preview-teleported` | whether to append image-viewer to body. A nested parent element attribute transform should have this attribute set to `true`. | `boolean` | `false` |
|
||||
|
||||
| Event Name | Description | Parameters |
|
||||
| ---------- | -------------------- | ---------- |
|
||||
| load | Same as native load | (e: Event) |
|
||||
| error | Same as native error | (e: Error) |
|
||||
### Image Events
|
||||
|
||||
## Image Slots
|
||||
| Name | Description | Type |
|
||||
| -------- | ------------------------------------------------------------------------------------------------- | ------------------------- |
|
||||
| `load` | same as native load. | `(e: Event) => void` |
|
||||
| `error` | same as native error. | `(e: Error) => void` |
|
||||
| `switch` | trigger when switching images. | `(index: number) => void` |
|
||||
| `close` | trigger when clicking on close button or when `hide-on-click-modal` enabled clicking on backdrop. | `() => void` |
|
||||
|
||||
| Name | Description |
|
||||
| ----------- | ------------------------------- |
|
||||
| placeholder | Triggers when image load |
|
||||
| error | Triggers when image load failed |
|
||||
### Image Slots
|
||||
|
||||
## ImageViewer Attributes
|
||||
| Name | Description |
|
||||
| ------------- | -------------------------------- |
|
||||
| `placeholder` | triggers when image load. |
|
||||
| `error` | triggers when image load failed. |
|
||||
| `viewer` | description of the image. |
|
||||
|
||||
| Attribute | Description | Type | Acceptable Value | Default |
|
||||
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------- | --------------- | ------------------- | ------- |
|
||||
| url-list | Preview link list | Array\<string\> | - | [] |
|
||||
| z-index | Preview backdrop z-index | number / string | int / string\<int\> | 2000 |
|
||||
| initial-index | The initial preview image index, less than or equal to the length of `url-list` | number | int | 0 |
|
||||
| infinite | Whether preview is infinite | boolean | true / false | true |
|
||||
| hide-on-click-modal | Whether user can emit close event when clicking backdrop | boolean | true / false | false |
|
||||
| teleported | whether to append image itself to body. A nested parent element attribute transform should have this attribute set to `true` | boolean | — | false |
|
||||
## Image Viewer API
|
||||
|
||||
## ImageViewer Events
|
||||
### Image Viewer Attributes
|
||||
|
||||
| Event name | Description | Callback parameter |
|
||||
| ---------- | ---------------------------------------------------------------------------------------------- | -------------------------------------- |
|
||||
| close | Emitted when clicking on `X` button or when `hide-on-click-modal` enabled clicking on backdrop | None |
|
||||
| switch | When switching images | `(val: number)` switching target index |
|
||||
| Name | Description | Type | Default |
|
||||
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------ | ------- |
|
||||
| `url-list` | preview link list. | `string[]` | `[]` |
|
||||
| `z-index` | preview backdrop z-index. | `number \| string` | — |
|
||||
| `initial-index` | the initial preview image index, less than or equal to the length of `url-list`. | `number` | `0` |
|
||||
| `infinite` | whether preview is infinite. | `boolean` | `true` |
|
||||
| `hide-on-click-modal` | whether user can emit close event when clicking backdrop. | `boolean` | `false` |
|
||||
| `teleported` | whether to append image itself to body. A nested parent element attribute transform should have this attribute set to `true`. | `boolean` | `false` |
|
||||
|
||||
### Image Viewer Events
|
||||
|
||||
| Event name | Description | Type |
|
||||
| ---------- | ------------------------------------------------------------------------------------------------- | ------------------------- |
|
||||
| `close` | trigger when clicking on close button or when `hide-on-click-modal` enabled clicking on backdrop. | `() => void` |
|
||||
| `switch` | trigger when switching images. | `(index: number) => void` |
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { buildProps, definePropType, mutable } from '@element-plus/utils'
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
|
||||
export type ImageViewerAction =
|
||||
| 'zoomIn'
|
||||
| 'zoomOut'
|
||||
| 'clockwise'
|
||||
| 'anticlockwise'
|
||||
|
||||
export const imageViewerProps = buildProps({
|
||||
urlList: {
|
||||
type: definePropType<string[]>(Array),
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
<!-- CLOSE -->
|
||||
<span :class="[ns.e('btn'), ns.e('close')]" @click="hide">
|
||||
<el-icon><close /></el-icon>
|
||||
<el-icon><Close /></el-icon>
|
||||
</span>
|
||||
|
||||
<!-- ARROW -->
|
||||
@ -24,7 +24,7 @@
|
||||
]"
|
||||
@click="prev"
|
||||
>
|
||||
<el-icon><arrow-left /></el-icon>
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
</span>
|
||||
<span
|
||||
:class="[
|
||||
@ -34,17 +34,17 @@
|
||||
]"
|
||||
@click="next"
|
||||
>
|
||||
<el-icon><arrow-right /></el-icon>
|
||||
<el-icon><ArrowRight /></el-icon>
|
||||
</span>
|
||||
</template>
|
||||
<!-- ACTIONS -->
|
||||
<div :class="[ns.e('btn'), ns.e('actions')]">
|
||||
<div :class="ns.e('actions__inner')">
|
||||
<el-icon @click="handleActions('zoomOut')">
|
||||
<zoom-out />
|
||||
<ZoomOut />
|
||||
</el-icon>
|
||||
<el-icon @click="handleActions('zoomIn')">
|
||||
<zoom-in />
|
||||
<ZoomIn />
|
||||
</el-icon>
|
||||
<i :class="ns.e('actions__divider')" />
|
||||
<el-icon @click="toggleMode">
|
||||
@ -52,10 +52,10 @@
|
||||
</el-icon>
|
||||
<i :class="ns.e('actions__divider')" />
|
||||
<el-icon @click="handleActions('anticlockwise')">
|
||||
<refresh-left />
|
||||
<RefreshLeft />
|
||||
</el-icon>
|
||||
<el-icon @click="handleActions('clockwise')">
|
||||
<refresh-right />
|
||||
<RefreshRight />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
@ -80,10 +80,9 @@
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
effectScope,
|
||||
markRaw,
|
||||
nextTick,
|
||||
@ -93,10 +92,10 @@ import {
|
||||
} from 'vue'
|
||||
import { isNumber, useEventListener } from '@vueuse/core'
|
||||
import { throttle } from 'lodash-unified'
|
||||
import ElIcon from '@element-plus/components/icon'
|
||||
import { useLocale, useNamespace, useZIndex } from '@element-plus/hooks'
|
||||
import { EVENT_CODE } from '@element-plus/constants'
|
||||
import { isFirefox } from '@element-plus/utils'
|
||||
import ElIcon from '@element-plus/components/icon'
|
||||
import {
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
@ -111,6 +110,7 @@ import {
|
||||
import { imageViewerEmits, imageViewerProps } from './image-viewer'
|
||||
|
||||
import type { CSSProperties } from 'vue'
|
||||
import type { ImageViewerAction } from './image-viewer'
|
||||
|
||||
const Mode = {
|
||||
CONTAIN: {
|
||||
@ -124,304 +124,262 @@ const Mode = {
|
||||
}
|
||||
|
||||
const mousewheelEventName = isFirefox() ? 'DOMMouseScroll' : 'mousewheel'
|
||||
export type ImageViewerAction =
|
||||
| 'zoomIn'
|
||||
| 'zoomOut'
|
||||
| 'clockwise'
|
||||
| 'anticlockwise'
|
||||
|
||||
export default defineComponent({
|
||||
defineOptions({
|
||||
name: 'ElImageViewer',
|
||||
components: {
|
||||
ElIcon,
|
||||
Close,
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
ZoomOut,
|
||||
ZoomIn,
|
||||
RefreshLeft,
|
||||
RefreshRight,
|
||||
},
|
||||
props: imageViewerProps,
|
||||
emits: imageViewerEmits,
|
||||
})
|
||||
|
||||
setup(props, { emit }) {
|
||||
const { t } = useLocale()
|
||||
const ns = useNamespace('image-viewer')
|
||||
const { nextZIndex } = useZIndex()
|
||||
const wrapper = ref<HTMLDivElement>()
|
||||
const imgRefs = ref<any[]>([])
|
||||
const props = defineProps(imageViewerProps)
|
||||
const emit = defineEmits(imageViewerEmits)
|
||||
|
||||
const scopeEventListener = effectScope()
|
||||
const { t } = useLocale()
|
||||
const ns = useNamespace('image-viewer')
|
||||
const { nextZIndex } = useZIndex()
|
||||
const wrapper = ref<HTMLDivElement>()
|
||||
const imgRefs = ref<any[]>([])
|
||||
|
||||
const loading = ref(true)
|
||||
const index = ref(props.initialIndex)
|
||||
const mode = ref(Mode.CONTAIN)
|
||||
const transform = ref({
|
||||
scale: 1,
|
||||
deg: 0,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
enableTransition: false,
|
||||
})
|
||||
const scopeEventListener = effectScope()
|
||||
|
||||
const isSingle = computed(() => {
|
||||
const { urlList } = props
|
||||
return urlList.length <= 1
|
||||
})
|
||||
const loading = ref(true)
|
||||
const index = ref(props.initialIndex)
|
||||
const mode = ref(Mode.CONTAIN)
|
||||
const transform = ref({
|
||||
scale: 1,
|
||||
deg: 0,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
enableTransition: false,
|
||||
})
|
||||
|
||||
const isFirst = computed(() => {
|
||||
return index.value === 0
|
||||
})
|
||||
const isSingle = computed(() => {
|
||||
const { urlList } = props
|
||||
return urlList.length <= 1
|
||||
})
|
||||
|
||||
const isLast = computed(() => {
|
||||
return index.value === props.urlList.length - 1
|
||||
})
|
||||
const isFirst = computed(() => {
|
||||
return index.value === 0
|
||||
})
|
||||
|
||||
const currentImg = computed(() => {
|
||||
return props.urlList[index.value]
|
||||
})
|
||||
const isLast = computed(() => {
|
||||
return index.value === props.urlList.length - 1
|
||||
})
|
||||
|
||||
const imgStyle = computed(() => {
|
||||
const { scale, deg, offsetX, offsetY, enableTransition } = transform.value
|
||||
let translateX = offsetX / scale
|
||||
let translateY = offsetY / scale
|
||||
const currentImg = computed(() => {
|
||||
return props.urlList[index.value]
|
||||
})
|
||||
|
||||
switch (deg % 360) {
|
||||
case 90:
|
||||
case -270:
|
||||
;[translateX, translateY] = [translateY, -translateX]
|
||||
break
|
||||
case 180:
|
||||
case -180:
|
||||
;[translateX, translateY] = [-translateX, -translateY]
|
||||
break
|
||||
case 270:
|
||||
case -90:
|
||||
;[translateX, translateY] = [-translateY, translateX]
|
||||
break
|
||||
}
|
||||
const imgStyle = computed(() => {
|
||||
const { scale, deg, offsetX, offsetY, enableTransition } = transform.value
|
||||
let translateX = offsetX / scale
|
||||
let translateY = offsetY / scale
|
||||
|
||||
const style: CSSProperties = {
|
||||
transform: `scale(${scale}) rotate(${deg}deg) translate(${translateX}px, ${translateY}px)`,
|
||||
transition: enableTransition ? 'transform .3s' : '',
|
||||
}
|
||||
if (mode.value.name === Mode.CONTAIN.name) {
|
||||
style.maxWidth = style.maxHeight = '100%'
|
||||
}
|
||||
return style
|
||||
})
|
||||
switch (deg % 360) {
|
||||
case 90:
|
||||
case -270:
|
||||
;[translateX, translateY] = [translateY, -translateX]
|
||||
break
|
||||
case 180:
|
||||
case -180:
|
||||
;[translateX, translateY] = [-translateX, -translateY]
|
||||
break
|
||||
case 270:
|
||||
case -90:
|
||||
;[translateX, translateY] = [-translateY, translateX]
|
||||
break
|
||||
}
|
||||
|
||||
const computedZIndex = computed(() => {
|
||||
return isNumber(props.zIndex) ? props.zIndex : nextZIndex()
|
||||
})
|
||||
const style: CSSProperties = {
|
||||
transform: `scale(${scale}) rotate(${deg}deg) translate(${translateX}px, ${translateY}px)`,
|
||||
transition: enableTransition ? 'transform .3s' : '',
|
||||
}
|
||||
if (mode.value.name === Mode.CONTAIN.name) {
|
||||
style.maxWidth = style.maxHeight = '100%'
|
||||
}
|
||||
return style
|
||||
})
|
||||
|
||||
function hide() {
|
||||
unregisterEventListener()
|
||||
emit('close')
|
||||
const computedZIndex = computed(() => {
|
||||
return isNumber(props.zIndex) ? props.zIndex : nextZIndex()
|
||||
})
|
||||
|
||||
function hide() {
|
||||
unregisterEventListener()
|
||||
emit('close')
|
||||
}
|
||||
|
||||
function registerEventListener() {
|
||||
const keydownHandler = throttle((e: KeyboardEvent) => {
|
||||
switch (e.code) {
|
||||
// ESC
|
||||
case EVENT_CODE.esc:
|
||||
hide()
|
||||
break
|
||||
// SPACE
|
||||
case EVENT_CODE.space:
|
||||
toggleMode()
|
||||
break
|
||||
// LEFT_ARROW
|
||||
case EVENT_CODE.left:
|
||||
prev()
|
||||
break
|
||||
// UP_ARROW
|
||||
case EVENT_CODE.up:
|
||||
handleActions('zoomIn')
|
||||
break
|
||||
// RIGHT_ARROW
|
||||
case EVENT_CODE.right:
|
||||
next()
|
||||
break
|
||||
// DOWN_ARROW
|
||||
case EVENT_CODE.down:
|
||||
handleActions('zoomOut')
|
||||
break
|
||||
}
|
||||
|
||||
function registerEventListener() {
|
||||
const keydownHandler = throttle((e: KeyboardEvent) => {
|
||||
switch (e.code) {
|
||||
// ESC
|
||||
case EVENT_CODE.esc:
|
||||
hide()
|
||||
break
|
||||
// SPACE
|
||||
case EVENT_CODE.space:
|
||||
toggleMode()
|
||||
break
|
||||
// LEFT_ARROW
|
||||
case EVENT_CODE.left:
|
||||
prev()
|
||||
break
|
||||
// UP_ARROW
|
||||
case EVENT_CODE.up:
|
||||
handleActions('zoomIn')
|
||||
break
|
||||
// RIGHT_ARROW
|
||||
case EVENT_CODE.right:
|
||||
next()
|
||||
break
|
||||
// DOWN_ARROW
|
||||
case EVENT_CODE.down:
|
||||
handleActions('zoomOut')
|
||||
break
|
||||
}
|
||||
})
|
||||
const mousewheelHandler = throttle(
|
||||
(e: WheelEvent | any /* TODO: wheelDelta is deprecated */) => {
|
||||
const delta = e.wheelDelta ? e.wheelDelta : -e.detail
|
||||
if (delta > 0) {
|
||||
handleActions('zoomIn', {
|
||||
zoomRate: 1.2,
|
||||
enableTransition: false,
|
||||
})
|
||||
} else {
|
||||
handleActions('zoomOut', {
|
||||
zoomRate: 1.2,
|
||||
enableTransition: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
scopeEventListener.run(() => {
|
||||
useEventListener(document, 'keydown', keydownHandler)
|
||||
useEventListener(document, mousewheelEventName, mousewheelHandler)
|
||||
})
|
||||
}
|
||||
|
||||
function unregisterEventListener() {
|
||||
scopeEventListener.stop()
|
||||
}
|
||||
|
||||
function handleImgLoad() {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
function handleImgError(e: Event) {
|
||||
loading.value = false
|
||||
;(e.target as HTMLImageElement).alt = t('el.image.error')
|
||||
}
|
||||
|
||||
function handleMouseDown(e: MouseEvent) {
|
||||
if (loading.value || e.button !== 0 || !wrapper.value) return
|
||||
transform.value.enableTransition = false
|
||||
|
||||
const { offsetX, offsetY } = transform.value
|
||||
const startX = e.pageX
|
||||
const startY = e.pageY
|
||||
|
||||
const dragHandler = throttle((ev: MouseEvent) => {
|
||||
transform.value = {
|
||||
...transform.value,
|
||||
offsetX: offsetX + ev.pageX - startX,
|
||||
offsetY: offsetY + ev.pageY - startY,
|
||||
}
|
||||
})
|
||||
const removeMousemove = useEventListener(
|
||||
document,
|
||||
'mousemove',
|
||||
dragHandler
|
||||
)
|
||||
useEventListener(document, 'mouseup', () => {
|
||||
removeMousemove()
|
||||
})
|
||||
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
function reset() {
|
||||
transform.value = {
|
||||
scale: 1,
|
||||
deg: 0,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
enableTransition: false,
|
||||
})
|
||||
const mousewheelHandler = throttle(
|
||||
(e: WheelEvent | any /* TODO: wheelDelta is deprecated */) => {
|
||||
const delta = e.wheelDelta ? e.wheelDelta : -e.detail
|
||||
if (delta > 0) {
|
||||
handleActions('zoomIn', {
|
||||
zoomRate: 1.2,
|
||||
enableTransition: false,
|
||||
})
|
||||
} else {
|
||||
handleActions('zoomOut', {
|
||||
zoomRate: 1.2,
|
||||
enableTransition: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
function toggleMode() {
|
||||
if (loading.value) return
|
||||
scopeEventListener.run(() => {
|
||||
useEventListener(document, 'keydown', keydownHandler)
|
||||
useEventListener(document, mousewheelEventName, mousewheelHandler)
|
||||
})
|
||||
}
|
||||
|
||||
const modeNames = Object.keys(Mode)
|
||||
const modeValues = Object.values(Mode)
|
||||
const currentMode = mode.value.name
|
||||
const index = modeValues.findIndex((i) => i.name === currentMode)
|
||||
const nextIndex = (index + 1) % modeNames.length
|
||||
mode.value = Mode[modeNames[nextIndex]]
|
||||
reset()
|
||||
function unregisterEventListener() {
|
||||
scopeEventListener.stop()
|
||||
}
|
||||
|
||||
function handleImgLoad() {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
function handleImgError(e: Event) {
|
||||
loading.value = false
|
||||
;(e.target as HTMLImageElement).alt = t('el.image.error')
|
||||
}
|
||||
|
||||
function handleMouseDown(e: MouseEvent) {
|
||||
if (loading.value || e.button !== 0 || !wrapper.value) return
|
||||
transform.value.enableTransition = false
|
||||
|
||||
const { offsetX, offsetY } = transform.value
|
||||
const startX = e.pageX
|
||||
const startY = e.pageY
|
||||
|
||||
const dragHandler = throttle((ev: MouseEvent) => {
|
||||
transform.value = {
|
||||
...transform.value,
|
||||
offsetX: offsetX + ev.pageX - startX,
|
||||
offsetY: offsetY + ev.pageY - startY,
|
||||
}
|
||||
})
|
||||
const removeMousemove = useEventListener(document, 'mousemove', dragHandler)
|
||||
useEventListener(document, 'mouseup', () => {
|
||||
removeMousemove()
|
||||
})
|
||||
|
||||
function prev() {
|
||||
if (isFirst.value && !props.infinite) return
|
||||
const len = props.urlList.length
|
||||
index.value = (index.value - 1 + len) % len
|
||||
}
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
function next() {
|
||||
if (isLast.value && !props.infinite) return
|
||||
const len = props.urlList.length
|
||||
index.value = (index.value + 1) % len
|
||||
}
|
||||
function reset() {
|
||||
transform.value = {
|
||||
scale: 1,
|
||||
deg: 0,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
enableTransition: false,
|
||||
}
|
||||
}
|
||||
|
||||
function handleActions(action: ImageViewerAction, options = {}) {
|
||||
if (loading.value) return
|
||||
const { zoomRate, rotateDeg, enableTransition } = {
|
||||
zoomRate: 1.4,
|
||||
rotateDeg: 90,
|
||||
enableTransition: true,
|
||||
...options,
|
||||
function toggleMode() {
|
||||
if (loading.value) return
|
||||
|
||||
const modeNames = Object.keys(Mode)
|
||||
const modeValues = Object.values(Mode)
|
||||
const currentMode = mode.value.name
|
||||
const index = modeValues.findIndex((i) => i.name === currentMode)
|
||||
const nextIndex = (index + 1) % modeNames.length
|
||||
mode.value = Mode[modeNames[nextIndex]]
|
||||
reset()
|
||||
}
|
||||
|
||||
function prev() {
|
||||
if (isFirst.value && !props.infinite) return
|
||||
const len = props.urlList.length
|
||||
index.value = (index.value - 1 + len) % len
|
||||
}
|
||||
|
||||
function next() {
|
||||
if (isLast.value && !props.infinite) return
|
||||
const len = props.urlList.length
|
||||
index.value = (index.value + 1) % len
|
||||
}
|
||||
|
||||
function handleActions(action: ImageViewerAction, options = {}) {
|
||||
if (loading.value) return
|
||||
const { zoomRate, rotateDeg, enableTransition } = {
|
||||
zoomRate: 1.4,
|
||||
rotateDeg: 90,
|
||||
enableTransition: true,
|
||||
...options,
|
||||
}
|
||||
switch (action) {
|
||||
case 'zoomOut':
|
||||
if (transform.value.scale > 0.2) {
|
||||
transform.value.scale = Number.parseFloat(
|
||||
(transform.value.scale / zoomRate).toFixed(3)
|
||||
)
|
||||
}
|
||||
switch (action) {
|
||||
case 'zoomOut':
|
||||
if (transform.value.scale > 0.2) {
|
||||
transform.value.scale = Number.parseFloat(
|
||||
(transform.value.scale / zoomRate).toFixed(3)
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'zoomIn':
|
||||
if (transform.value.scale < 7) {
|
||||
transform.value.scale = Number.parseFloat(
|
||||
(transform.value.scale * zoomRate).toFixed(3)
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'clockwise':
|
||||
transform.value.deg += rotateDeg
|
||||
break
|
||||
case 'anticlockwise':
|
||||
transform.value.deg -= rotateDeg
|
||||
break
|
||||
break
|
||||
case 'zoomIn':
|
||||
if (transform.value.scale < 7) {
|
||||
transform.value.scale = Number.parseFloat(
|
||||
(transform.value.scale * zoomRate).toFixed(3)
|
||||
)
|
||||
}
|
||||
transform.value.enableTransition = enableTransition
|
||||
break
|
||||
case 'clockwise':
|
||||
transform.value.deg += rotateDeg
|
||||
break
|
||||
case 'anticlockwise':
|
||||
transform.value.deg -= rotateDeg
|
||||
break
|
||||
}
|
||||
transform.value.enableTransition = enableTransition
|
||||
}
|
||||
|
||||
watch(currentImg, () => {
|
||||
nextTick(() => {
|
||||
const $img = imgRefs.value[0]
|
||||
if (!$img?.complete) {
|
||||
loading.value = true
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
watch(currentImg, () => {
|
||||
nextTick(() => {
|
||||
const $img = imgRefs.value[0]
|
||||
if (!$img?.complete) {
|
||||
loading.value = true
|
||||
}
|
||||
})
|
||||
})
|
||||
watch(index, (val) => {
|
||||
reset()
|
||||
emit('switch', val)
|
||||
})
|
||||
|
||||
watch(index, (val) => {
|
||||
reset()
|
||||
emit('switch', val)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
registerEventListener()
|
||||
// add tabindex then wrapper can be focusable via Javascript
|
||||
// focus wrapper so arrow key can't cause inner scroll behavior underneath
|
||||
wrapper.value?.focus?.()
|
||||
})
|
||||
|
||||
return {
|
||||
index,
|
||||
wrapper,
|
||||
imgRefs,
|
||||
isSingle,
|
||||
isFirst,
|
||||
isLast,
|
||||
currentImg,
|
||||
imgStyle,
|
||||
mode,
|
||||
computedZIndex,
|
||||
handleActions,
|
||||
prev,
|
||||
next,
|
||||
hide,
|
||||
toggleMode,
|
||||
handleImgLoad,
|
||||
handleImgError,
|
||||
handleMouseDown,
|
||||
ns,
|
||||
}
|
||||
},
|
||||
onMounted(() => {
|
||||
registerEventListener()
|
||||
// add tabindex then wrapper can be focusable via Javascript
|
||||
// focus wrapper so arrow key can't cause inner scroll behavior underneath
|
||||
wrapper.value?.focus?.()
|
||||
})
|
||||
</script>
|
||||
|
@ -6,6 +6,12 @@ import {
|
||||
mockImageEvent,
|
||||
} from '@element-plus/test-utils/mock'
|
||||
import Image from '../src/image.vue'
|
||||
import type { AnchorHTMLAttributes, ImgHTMLAttributes } from 'vue'
|
||||
import type { ImageProps } from '../src/image'
|
||||
|
||||
type ElImageProps = ImgHTMLAttributes &
|
||||
AnchorHTMLAttributes &
|
||||
Partial<ImageProps>
|
||||
|
||||
// firstly wait for image event
|
||||
// secondly wait for vue render
|
||||
@ -24,10 +30,13 @@ describe('Image.vue', () => {
|
||||
|
||||
test('image load success test', async () => {
|
||||
const alt = 'this ia alt'
|
||||
const wrapper = mount(Image, {
|
||||
props: {
|
||||
src: IMAGE_SUCCESS,
|
||||
alt,
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
const props: ElImageProps = {
|
||||
alt,
|
||||
src: IMAGE_SUCCESS,
|
||||
}
|
||||
return () => <Image {...props} />
|
||||
},
|
||||
})
|
||||
expect(wrapper.find('.el-image__placeholder').exists()).toBe(true)
|
||||
@ -70,11 +79,9 @@ describe('Image.vue', () => {
|
||||
})
|
||||
|
||||
test('imageStyle fit test', async () => {
|
||||
const fits = ['fill', 'contain', 'cover', 'none', 'scale-down']
|
||||
const fits = ['fill', 'contain', 'cover', 'none', 'scale-down'] as const
|
||||
for (const fit of fits) {
|
||||
const wrapper = mount(Image, {
|
||||
props: { fit, src: IMAGE_SUCCESS },
|
||||
})
|
||||
const wrapper = mount(() => <Image src={IMAGE_SUCCESS} fit={fit} />)
|
||||
await doubleWait()
|
||||
expect(wrapper.find('img').attributes('style')).toContain(
|
||||
`object-fit: ${fit};`
|
||||
@ -83,25 +90,23 @@ describe('Image.vue', () => {
|
||||
})
|
||||
|
||||
test('preview classname test', async () => {
|
||||
const wrapper = mount(Image, {
|
||||
props: {
|
||||
fit: 'cover',
|
||||
src: IMAGE_SUCCESS,
|
||||
previewSrcList: Array.from({ length: 3 }).fill(IMAGE_SUCCESS),
|
||||
},
|
||||
})
|
||||
const props: ElImageProps = {
|
||||
fit: 'cover',
|
||||
src: IMAGE_SUCCESS,
|
||||
previewSrcList: Array.from<string>({ length: 3 }).fill(IMAGE_SUCCESS),
|
||||
}
|
||||
const wrapper = mount(() => <Image {...props} />)
|
||||
await doubleWait()
|
||||
expect(wrapper.find('img').classes()).toContain('el-image__preview')
|
||||
})
|
||||
|
||||
test('preview initial index test', async () => {
|
||||
const wrapper = mount(Image, {
|
||||
props: {
|
||||
src: IMAGE_SUCCESS,
|
||||
previewSrcList: Array.from({ length: 3 }).fill(IMAGE_FAIL),
|
||||
initialIndex: 1,
|
||||
},
|
||||
})
|
||||
const props: ElImageProps = {
|
||||
src: IMAGE_SUCCESS,
|
||||
previewSrcList: Array.from<string>({ length: 3 }).fill(IMAGE_FAIL),
|
||||
initialIndex: 1,
|
||||
}
|
||||
const wrapper = mount(() => <Image {...props} />)
|
||||
await doubleWait()
|
||||
await wrapper.find('.el-image__inner').trigger('click')
|
||||
expect(
|
||||
@ -111,13 +116,12 @@ describe('Image.vue', () => {
|
||||
|
||||
test('$attrs', async () => {
|
||||
const alt = 'this ia alt'
|
||||
const wrapper = mount(Image, {
|
||||
props: {
|
||||
src: IMAGE_SUCCESS,
|
||||
alt,
|
||||
referrerpolicy: 'origin',
|
||||
},
|
||||
})
|
||||
const props: ElImageProps = {
|
||||
alt,
|
||||
src: IMAGE_SUCCESS,
|
||||
referrerpolicy: 'origin',
|
||||
}
|
||||
const wrapper = mount(() => <Image {...props} />)
|
||||
await doubleWait()
|
||||
expect(wrapper.find('img').attributes('alt')).toBe(alt)
|
||||
expect(wrapper.find('img').attributes('referrerpolicy')).toBe('origin')
|
||||
@ -125,12 +129,11 @@ describe('Image.vue', () => {
|
||||
|
||||
test('pass event listeners', async () => {
|
||||
let result = false
|
||||
const wrapper = mount(Image, {
|
||||
props: {
|
||||
src: IMAGE_SUCCESS,
|
||||
onClick: () => (result = true),
|
||||
},
|
||||
})
|
||||
const props: ElImageProps = {
|
||||
src: IMAGE_SUCCESS,
|
||||
onClick: () => (result = true),
|
||||
}
|
||||
const wrapper = mount(() => <Image {...props} />)
|
||||
await doubleWait()
|
||||
await wrapper.find('.el-image__inner').trigger('click')
|
||||
expect(result).toBeTruthy()
|
||||
@ -138,12 +141,11 @@ describe('Image.vue', () => {
|
||||
|
||||
test('emit load event', async () => {
|
||||
const handleLoad = jest.fn()
|
||||
const wrapper = mount(Image, {
|
||||
props: {
|
||||
src: IMAGE_SUCCESS,
|
||||
onLoad: handleLoad,
|
||||
},
|
||||
})
|
||||
const props: ElImageProps = {
|
||||
src: IMAGE_SUCCESS,
|
||||
onLoad: handleLoad,
|
||||
}
|
||||
const wrapper = mount(() => <Image {...props} />)
|
||||
await doubleWait()
|
||||
expect(wrapper.find('.el-image__inner').exists()).toBe(true)
|
||||
expect(handleLoad).toBeCalled()
|
@ -33,9 +33,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, nextTick, onMounted, ref, watch } from 'vue'
|
||||
import { isString } from '@vue/shared'
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||||
import {
|
||||
isBoolean,
|
||||
isClient,
|
||||
@ -53,244 +52,220 @@ import {
|
||||
getScrollContainer,
|
||||
isElement,
|
||||
isInContainer,
|
||||
isString,
|
||||
} from '@element-plus/utils'
|
||||
import { imageEmits, imageProps } from './image'
|
||||
|
||||
import type { CSSProperties, StyleValue } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'ElImage',
|
||||
})
|
||||
|
||||
const props = defineProps(imageProps)
|
||||
const emit = defineEmits(imageEmits)
|
||||
|
||||
let prevOverflow = ''
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ElImage',
|
||||
components: {
|
||||
ImageViewer,
|
||||
useDeprecated(
|
||||
{
|
||||
scope: 'el-image',
|
||||
from: 'append-to-body',
|
||||
replacement: 'preview-teleported',
|
||||
version: '2.2.0',
|
||||
ref: 'https://element-plus.org/en-US/component/image.html#image-attributess',
|
||||
},
|
||||
inheritAttrs: false,
|
||||
computed(() => isBoolean(props.appendToBody))
|
||||
)
|
||||
|
||||
props: imageProps,
|
||||
emits: imageEmits,
|
||||
const { t } = useLocale()
|
||||
const ns = useNamespace('image')
|
||||
|
||||
setup(props, { emit, attrs: rawAttrs }) {
|
||||
useDeprecated(
|
||||
{
|
||||
scope: 'el-image',
|
||||
from: 'append-to-body',
|
||||
replacement: 'preview-teleported',
|
||||
version: '2.2.0',
|
||||
ref: 'https://element-plus.org/en-US/component/image.html#image-attributess',
|
||||
},
|
||||
computed(() => isBoolean(props.appendToBody))
|
||||
const attrs = useAttrs()
|
||||
const hasLoadError = ref(false)
|
||||
const loading = ref(true)
|
||||
const imgWidth = ref(0)
|
||||
const imgHeight = ref(0)
|
||||
const showViewer = ref(false)
|
||||
const container = ref<HTMLElement>()
|
||||
|
||||
const _scrollContainer = ref<HTMLElement | Window>()
|
||||
let stopScrollListener: () => void
|
||||
let stopWheelListener: () => void
|
||||
|
||||
const containerStyle = computed(() => attrs.value.style as StyleValue)
|
||||
|
||||
const imageStyle = computed<CSSProperties>(() => {
|
||||
const { fit } = props
|
||||
if (isClient && fit) {
|
||||
return { objectFit: fit }
|
||||
}
|
||||
return {}
|
||||
})
|
||||
|
||||
const preview = computed(() => {
|
||||
const { previewSrcList } = props
|
||||
return Array.isArray(previewSrcList) && previewSrcList.length > 0
|
||||
})
|
||||
|
||||
const teleported = computed(() => {
|
||||
return props.appendToBody || props.previewTeleported
|
||||
})
|
||||
|
||||
const imageIndex = computed(() => {
|
||||
const { previewSrcList, initialIndex } = props
|
||||
let previewIndex = initialIndex
|
||||
if (initialIndex > previewSrcList.length - 1) {
|
||||
previewIndex = 0
|
||||
}
|
||||
return previewIndex
|
||||
})
|
||||
|
||||
const loadImage = () => {
|
||||
if (!isClient) return
|
||||
|
||||
// reset status
|
||||
loading.value = true
|
||||
hasLoadError.value = false
|
||||
|
||||
const img = new Image()
|
||||
const currentImageSrc = props.src
|
||||
|
||||
// load & error callbacks are only responsible for currentImageSrc
|
||||
img.addEventListener('load', (e) => {
|
||||
if (currentImageSrc !== props.src) {
|
||||
return
|
||||
}
|
||||
handleLoad(e, img)
|
||||
})
|
||||
img.addEventListener('error', (e) => {
|
||||
if (currentImageSrc !== props.src) {
|
||||
return
|
||||
}
|
||||
handleError(e)
|
||||
})
|
||||
|
||||
// bind html attrs
|
||||
// so it can behave consistently
|
||||
Object.entries(attrs.value).forEach(([key, value]) => {
|
||||
// avoid onload to be overwritten
|
||||
if (key.toLowerCase() === 'onload') return
|
||||
img.setAttribute(key, value as string)
|
||||
})
|
||||
img.src = currentImageSrc
|
||||
}
|
||||
|
||||
function handleLoad(e: Event, img: HTMLImageElement) {
|
||||
imgWidth.value = img.width
|
||||
imgHeight.value = img.height
|
||||
loading.value = false
|
||||
hasLoadError.value = false
|
||||
}
|
||||
|
||||
function handleError(event: Event) {
|
||||
loading.value = false
|
||||
hasLoadError.value = true
|
||||
emit('error', event)
|
||||
}
|
||||
|
||||
function handleLazyLoad() {
|
||||
if (isInContainer(container.value, _scrollContainer.value)) {
|
||||
loadImage()
|
||||
removeLazyLoadListener()
|
||||
}
|
||||
}
|
||||
|
||||
const lazyLoadHandler = useThrottleFn(handleLazyLoad, 200)
|
||||
|
||||
async function addLazyLoadListener() {
|
||||
if (!isClient) return
|
||||
|
||||
await nextTick()
|
||||
|
||||
const { scrollContainer } = props
|
||||
if (isElement(scrollContainer)) {
|
||||
_scrollContainer.value = scrollContainer
|
||||
} else if (isString(scrollContainer) && scrollContainer !== '') {
|
||||
_scrollContainer.value =
|
||||
document.querySelector<HTMLElement>(scrollContainer) ?? undefined
|
||||
} else if (container.value) {
|
||||
_scrollContainer.value = getScrollContainer(container.value)
|
||||
}
|
||||
|
||||
if (_scrollContainer.value) {
|
||||
stopScrollListener = useEventListener(
|
||||
_scrollContainer,
|
||||
'scroll',
|
||||
lazyLoadHandler
|
||||
)
|
||||
setTimeout(() => handleLazyLoad(), 100)
|
||||
}
|
||||
}
|
||||
|
||||
const { t } = useLocale()
|
||||
const ns = useNamespace('image')
|
||||
function removeLazyLoadListener() {
|
||||
if (!isClient || !_scrollContainer.value || !lazyLoadHandler) return
|
||||
|
||||
const attrs = useAttrs()
|
||||
const hasLoadError = ref(false)
|
||||
const loading = ref(true)
|
||||
const imgWidth = ref(0)
|
||||
const imgHeight = ref(0)
|
||||
const showViewer = ref(false)
|
||||
const container = ref<HTMLElement>()
|
||||
stopScrollListener()
|
||||
_scrollContainer.value = undefined
|
||||
}
|
||||
|
||||
const _scrollContainer = ref<HTMLElement | Window>()
|
||||
let stopScrollListener: () => void
|
||||
let stopWheelListener: () => void
|
||||
function wheelHandler(e: WheelEvent) {
|
||||
if (!e.ctrlKey) return
|
||||
|
||||
const containerStyle = computed(() => rawAttrs.style as StyleValue)
|
||||
if (e.deltaY < 0) {
|
||||
e.preventDefault()
|
||||
return false
|
||||
} else if (e.deltaY > 0) {
|
||||
e.preventDefault()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const imageStyle = computed<CSSProperties>(() => {
|
||||
const { fit } = props
|
||||
if (isClient && fit) {
|
||||
return { objectFit: fit }
|
||||
}
|
||||
return {}
|
||||
})
|
||||
function clickHandler() {
|
||||
// don't show viewer when preview is false
|
||||
if (!preview.value) return
|
||||
|
||||
const preview = computed(() => {
|
||||
const { previewSrcList } = props
|
||||
return Array.isArray(previewSrcList) && previewSrcList.length > 0
|
||||
})
|
||||
stopWheelListener = useEventListener('wheel', wheelHandler, {
|
||||
passive: false,
|
||||
})
|
||||
|
||||
const teleported = computed(() => {
|
||||
return props.appendToBody || props.previewTeleported
|
||||
})
|
||||
// prevent body scroll
|
||||
prevOverflow = document.body.style.overflow
|
||||
document.body.style.overflow = 'hidden'
|
||||
showViewer.value = true
|
||||
}
|
||||
|
||||
const imageIndex = computed(() => {
|
||||
const { previewSrcList, initialIndex } = props
|
||||
let previewIndex = initialIndex
|
||||
if (initialIndex > previewSrcList.length - 1) {
|
||||
previewIndex = 0
|
||||
}
|
||||
return previewIndex
|
||||
})
|
||||
function closeViewer() {
|
||||
stopWheelListener?.()
|
||||
document.body.style.overflow = prevOverflow
|
||||
showViewer.value = false
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const loadImage = () => {
|
||||
if (!isClient) return
|
||||
function switchViewer(val: number) {
|
||||
emit('switch', val)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.src,
|
||||
() => {
|
||||
if (props.lazy) {
|
||||
// reset status
|
||||
loading.value = true
|
||||
hasLoadError.value = false
|
||||
|
||||
const img = new Image()
|
||||
const currentImageSrc = props.src
|
||||
|
||||
// load & error callbacks are only responsible for currentImageSrc
|
||||
img.addEventListener('load', (e) => {
|
||||
if (currentImageSrc !== props.src) {
|
||||
return
|
||||
}
|
||||
handleLoad(e, img)
|
||||
})
|
||||
img.addEventListener('error', (e) => {
|
||||
if (currentImageSrc !== props.src) {
|
||||
return
|
||||
}
|
||||
handleError(e)
|
||||
})
|
||||
|
||||
// bind html attrs
|
||||
// so it can behave consistently
|
||||
Object.entries(attrs.value).forEach(([key, value]) => {
|
||||
// avoid onload to be overwritten
|
||||
if (key.toLowerCase() === 'onload') return
|
||||
img.setAttribute(key, value as string)
|
||||
})
|
||||
img.src = currentImageSrc
|
||||
removeLazyLoadListener()
|
||||
addLazyLoadListener()
|
||||
} else {
|
||||
loadImage()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
function handleLoad(e: Event, img: HTMLImageElement) {
|
||||
imgWidth.value = img.width
|
||||
imgHeight.value = img.height
|
||||
loading.value = false
|
||||
hasLoadError.value = false
|
||||
}
|
||||
|
||||
function handleError(event: Event) {
|
||||
loading.value = false
|
||||
hasLoadError.value = true
|
||||
emit('error', event)
|
||||
}
|
||||
|
||||
function handleLazyLoad() {
|
||||
if (isInContainer(container.value, _scrollContainer.value)) {
|
||||
loadImage()
|
||||
removeLazyLoadListener()
|
||||
}
|
||||
}
|
||||
|
||||
const lazyLoadHandler = useThrottleFn(handleLazyLoad, 200)
|
||||
|
||||
async function addLazyLoadListener() {
|
||||
if (!isClient) return
|
||||
|
||||
await nextTick()
|
||||
|
||||
const { scrollContainer } = props
|
||||
if (isElement(scrollContainer)) {
|
||||
_scrollContainer.value = scrollContainer
|
||||
} else if (isString(scrollContainer) && scrollContainer !== '') {
|
||||
_scrollContainer.value =
|
||||
document.querySelector<HTMLElement>(scrollContainer) ?? undefined
|
||||
} else if (container.value) {
|
||||
_scrollContainer.value = getScrollContainer(container.value)
|
||||
}
|
||||
|
||||
if (_scrollContainer.value) {
|
||||
stopScrollListener = useEventListener(
|
||||
_scrollContainer,
|
||||
'scroll',
|
||||
lazyLoadHandler
|
||||
)
|
||||
setTimeout(() => handleLazyLoad(), 100)
|
||||
}
|
||||
}
|
||||
|
||||
function removeLazyLoadListener() {
|
||||
if (!isClient || !_scrollContainer.value || !lazyLoadHandler) return
|
||||
|
||||
stopScrollListener()
|
||||
_scrollContainer.value = undefined
|
||||
}
|
||||
|
||||
function wheelHandler(e: WheelEvent) {
|
||||
if (!e.ctrlKey) return
|
||||
|
||||
if (e.deltaY < 0) {
|
||||
e.preventDefault()
|
||||
return false
|
||||
} else if (e.deltaY > 0) {
|
||||
e.preventDefault()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function clickHandler() {
|
||||
// don't show viewer when preview is false
|
||||
if (!preview.value) return
|
||||
|
||||
stopWheelListener = useEventListener('wheel', wheelHandler, {
|
||||
passive: false,
|
||||
})
|
||||
|
||||
// prevent body scroll
|
||||
prevOverflow = document.body.style.overflow
|
||||
document.body.style.overflow = 'hidden'
|
||||
showViewer.value = true
|
||||
}
|
||||
|
||||
function closeViewer() {
|
||||
stopWheelListener?.()
|
||||
document.body.style.overflow = prevOverflow
|
||||
showViewer.value = false
|
||||
emit('close')
|
||||
}
|
||||
|
||||
function switchViewer(val: number) {
|
||||
emit('switch', val)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.src,
|
||||
() => {
|
||||
if (props.lazy) {
|
||||
// reset status
|
||||
loading.value = true
|
||||
hasLoadError.value = false
|
||||
removeLazyLoadListener()
|
||||
addLazyLoadListener()
|
||||
} else {
|
||||
loadImage()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
if (props.lazy) {
|
||||
addLazyLoadListener()
|
||||
} else {
|
||||
loadImage()
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
attrs,
|
||||
loading,
|
||||
hasLoadError,
|
||||
showViewer,
|
||||
containerStyle,
|
||||
imageStyle,
|
||||
preview,
|
||||
imageIndex,
|
||||
container,
|
||||
ns,
|
||||
teleported,
|
||||
|
||||
clickHandler,
|
||||
closeViewer,
|
||||
switchViewer,
|
||||
t,
|
||||
}
|
||||
},
|
||||
onMounted(() => {
|
||||
if (props.lazy) {
|
||||
addLazyLoadListener()
|
||||
} else {
|
||||
loadImage()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
Loading…
Reference in New Issue
Block a user