feat: add image component (#124)

This commit is contained in:
SunLn 2020-08-27 21:22:32 +08:00 committed by GitHub
parent 2b2d085992
commit 9ca93eaa40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1021 additions and 7 deletions

View File

@ -78,3 +78,4 @@ describe('Avatar.vue', () => {
}
})
})

View File

@ -13,6 +13,7 @@ import ElTimeline from '@element-plus/timeline'
import ElProgress from '@element-plus/progress'
import ElBreadcrumb from '@element-plus/breadcrumb'
import ElIcon from '@element-plus/icon'
import ElImage from '@element-plus/image'
import ElLink from '@element-plus/link'
import ElRate from '@element-plus/rate'
import ElSwitch from '@element-plus/switch'
@ -42,6 +43,7 @@ export {
ElProgress,
ElBreadcrumb,
ElIcon,
ElImage,
ElLink,
ElRate,
ElSwitch,
@ -71,6 +73,7 @@ export default function install(app: App): void {
ElProgress(app)
ElBreadcrumb(app)
ElIcon(app)
ElImage(app)
ElLink(app)
ElRate(app)
ElSwitch(app)

View File

@ -28,6 +28,7 @@
"@element-plus/link": "^0.0.0",
"@element-plus/progress": "^0.0.0",
"@element-plus/rate": "^0.0.0",
"@element-plus/image": "^0.0.0",
"@element-plus/switch": "^0.0.0",
"@element-plus/radio": "^0.0.0",
"@element-plus/page-header": "^0.0.0",

View File

@ -0,0 +1,123 @@
import { mount } from '@vue/test-utils'
import Image from '../src/index.vue'
import { nextTick } from 'vue'
import { IMAGE_SUCCESS, IMAGE_FAIL } from '../../test-utils/mock'
describe('Image.vue', () => {
beforeAll(() => {
Object.defineProperty(global.Image.prototype, 'src', {
set(src) {
const type = !src || src === IMAGE_FAIL ? 'error' : 'load'
const event = new Event(type)
this.dispatchEvent(event)
},
})
})
test('render test', () => {
const wrapper = mount(Image)
expect(wrapper.find('.el-image').exists()).toBe(true)
})
test('image load success test', async () => {
const alt = 'this ia alt'
const wrapper = mount(Image, {
props: {
src: IMAGE_SUCCESS,
alt,
},
})
expect(wrapper.find('.el-image__placeholder').exists()).toBe(true)
await nextTick()
expect(wrapper.find('.el-image__inner').exists()).toBe(true)
expect(wrapper.find('img').exists()).toBe(true)
expect(wrapper.find('.el-image__placeholder').exists()).toBe(false)
expect(wrapper.find('.el-image__error').exists()).toBe(false)
})
test('image load error test', async () => {
const wrapper = mount(Image, {
props: {
src: IMAGE_FAIL,
},
})
expect(wrapper.emitted('error')).toBeDefined()
await nextTick()
expect(wrapper.find('.el-image__inner').exists()).toBe(false)
expect(wrapper.find('img').exists()).toBe(false)
expect(wrapper.find('.el-image__error').exists()).toBe(true)
})
test('imageStyle fit test', async () => {
const fits = ['fill', 'contain', 'cover', 'none', 'scale-down']
for (const fit of fits) {
const wrapper = mount(Image, {
props: { fit, src: IMAGE_SUCCESS },
})
await nextTick()
expect(wrapper.find('img').attributes('style')).toContain(`object-fit: ${fit};`)
}
})
test('preview classname test', async () => {
const wrapper = mount(Image, {
props: {
fit: 'cover',
src: IMAGE_SUCCESS,
previewSrcList: new Array(3).fill(IMAGE_SUCCESS),
},
})
await nextTick()
expect(wrapper.find('img').classes()).toContain('el-image__preview')
})
test('$attrs', async () => {
const alt = 'this ia alt'
const wrapper = mount(Image, {
props: {
src: IMAGE_SUCCESS,
alt,
referrerpolicy: 'origin',
},
})
await nextTick()
expect(wrapper.find('img').attributes('alt')).toBe(alt)
expect(wrapper.find('img').attributes('referrerpolicy')).toBe('origin')
})
test('pass event listeners', async() => {
let result = false
const wrapper = mount(Image, {
props: {
src: IMAGE_SUCCESS,
onClick: () => result = true,
},
})
await nextTick()
const inner = wrapper.find('.el-image__inner').element as HTMLElement
inner.click()
expect(result).toBeTruthy()
})
//@todo lazy image test
test('big image preview', async() => {
const wrapper = mount(Image, {
props: {
src: IMAGE_SUCCESS,
previewSrcList: [IMAGE_SUCCESS],
},
})
await nextTick()
expect(wrapper.find('.el-image__inner').exists()).toBe(true)
await wrapper.find('.el-image__inner').trigger('click')
const viewer = wrapper.find('.el-image-viewer__wrapper')
expect(viewer.exists()).toBe(true)
await wrapper.find('.el-image-viewer__close').trigger('click')
expect(wrapper.find('.el-image-viewer__wrapper').exists()).toBe(false)
})
})

View File

@ -0,0 +1,40 @@
<template>
<div class="demo-image">
<div v-for="fit in fits" :key="fit" class="block">
<div class="demonstration">{{ fit }}</div>
<br>
<el-image
style="width: 100px; height: 100px"
:src="url"
:fit="fit"
alt="111"
/>
</div>
</div>
</template>
<script lang="ts">
export default {
data() {
return {
fits: ['fill', 'contain', 'cover', 'none', 'scale-down'],
url: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg',
}
},
}
</script>
<style scoped>
.demo-image {
display: flex;
align-items: center;
justify-content: center;
}
.block {
margin: 0 20px;
text-align: center;
}
.demo-image .demonstration {
margin-bottom: 20px;
}
</style>

View File

@ -0,0 +1,26 @@
<template>
<div class="demo-image__preview">
<el-image
style="width: 100px; height: 100px"
:src="url"
:preview-src-list="srcList"
/>
</div>
</template>
<script>
export default {
data() {
return {
url: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg',
srcList: [
'https://fuss10.elemecdn.com/8/27/f01c15bb73e1ef3793e64e6b7bbccjpeg.jpeg',
'https://fuss10.elemecdn.com/1/8e/aeffeb4de74e2fde4bd74fc7b4486jpeg.jpeg',
],
}
},
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,54 @@
<template>
<div class="demo-image__error">
<div class="block">
<span class="demonstration">默认</span>
<el-image />
</div>
<div class="block">
<span class="demonstration">自定义</span>
<el-image>
<template #error>
<div class="image-slot">
<i class="el-icon-picture-outline"></i>
</div>
</template>
</el-image>
</div>
</div>
</template>
<style scoped>
.demo-image__error .block {
padding: 30px 0;
text-align: center;
border-right: 1px solid #eff2f6;
display: inline-block;
width: 49%;
box-sizing: border-box;
vertical-align: top;
margin: 0;
}
.demo-image__error .image-slot {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background: #f5f7fa;
color: #909399;
font-size: 14px;
margin: 0;
}
.demo-image__error .demonstration {
display: block;
color: #8492a6;
font-size: 14px;
margin-bottom: 20px;
}
.demo-image__error .el-image {
width: 300px;
height: 200px;
}
</style>

View File

@ -0,0 +1,10 @@
export { default as BasicUsage } from './basic.vue'
export { default as LoadError } from './error.vue'
export { default as LoadPlaceholder } from './placeholder.vue'
export { default as LazyLoad } from './lazyload.vue'
export { default as BigImage } from './bigImage.vue'
export default {
title: 'Image',
}

View File

@ -0,0 +1,44 @@
<template>
<div class="demo-image__lazy" style="overflow: auto;height: 400px;">
<el-image
v-for="url in urls"
:key="url"
:src="url"
lazy
/>
</div>
</template>
<script>
export default {
data() {
return {
urls: [
'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg',
'https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg',
'https://fuss10.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg',
'https://fuss10.elemecdn.com/3/28/bbf893f792f03a54408b3b7a7ebf0jpeg.jpeg',
'https://fuss10.elemecdn.com/2/11/6535bcfb26e4c79b48ddde44f4b6fjpeg.jpeg',
],
}
},
}
</script>
<style scoped>
.demo-image__lazy {
height: 400px;
overflow: auto;
}
.demo-image__lazy .el-image {
display: block;
min-height: 250px;
margin-bottom: 10px;
width: 50%;
}
.demo-image__lazy .el-image:last-child {
margin-bottom: 0;
}
</style>

View File

@ -0,0 +1,62 @@
<template>
<div class="demo-image__placeholder">
<div class="block">
<span class="demonstration">默认</span>
<el-image :src="src" />
</div>
<div class="block">
<span class="demonstration">自定义</span>
<el-image :src="src">
<template #placeholder>
<div class="image-slot">
加载中<span class="dot">...</span>
</div>
</template>
</el-image>
</div>
</div>
</template>
<script>
export default {
data() {
return {
src: 'https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg',
}
},
}
</script>
<style scoped>
.demo-image__placeholder .block {
padding: 30px 0;
text-align: center;
border-right: 1px solid #eff2f6;
display: inline-block;
width: 49%;
box-sizing: border-box;
vertical-align: top;
margin: 0;
}
.demo-image__placeholder .image-slot {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background: #f5f7fa;
color: #909399;
font-size: 14px;
margin: 0;
}
.demo-image__placeholder .el-image {
width: 300px;
height: 200px;
}
.demo-image__placeholder .demonstration {
display: block;
color: #8492a6;
font-size: 14px;
margin-bottom: 20px;
}
</style>

5
packages/image/index.ts Normal file
View File

@ -0,0 +1,5 @@
import { App } from 'vue'
import Image from './src/index.vue'
export default (app: App): void => {
app.component(Image.name, Image)
}

View File

@ -0,0 +1,12 @@
{
"name": "@element-plus/image",
"version": "0.0.0",
"main": "dist/index.js",
"license": "MIT",
"peerDependencies": {
"vue": "^3.0.0-rc.1"
},
"devDependencies": {
"@vue/test-utils": "^2.0.0-beta.0"
}
}

View File

@ -0,0 +1,357 @@
<template>
<transition name="viewer-fade">
<div
ref="wrapper"
tabindex="-1"
class="el-image-viewer__wrapper"
:style="{ 'z-index': zIndex }"
>
<div class="el-image-viewer__mask"></div>
<!-- CLOSE -->
<span class="el-image-viewer__btn el-image-viewer__close" @click="hide">
<i class="el-icon-circle-close"></i>
</span>
<!-- ARROW -->
<template v-if="!isSingle">
<span
class="el-image-viewer__btn el-image-viewer__prev"
:class="{ 'is-disabled': !infinite && isFirst }"
@click="prev"
>
<i class="el-icon-arrow-left"></i>
</span>
<span
class="el-image-viewer__btn el-image-viewer__next"
:class="{ 'is-disabled': !infinite && isLast }"
@click="next"
>
<i class="el-icon-arrow-right"></i>
</span>
</template>
<!-- ACTIONS -->
<div class="el-image-viewer__btn el-image-viewer__actions">
<div class="el-image-viewer__actions__inner">
<i class="el-icon-zoom-out" @click="handleActions('zoomOut')"></i>
<i class="el-icon-zoom-in" @click="handleActions('zoomIn')"></i>
<i class="el-image-viewer__actions__divider"></i>
<i :class="mode.icon" @click="toggleMode"></i>
<i class="el-image-viewer__actions__divider"></i>
<i class="el-icon-refresh-left" @click="handleActions('anticlocelise')"></i>
<i class="el-icon-refresh-right" @click="handleActions('clocelise')"></i>
</div>
</div>
<!-- CANVAS -->
<div class="el-image-viewer__canvas">
<img
v-for="(url, i) in urlList"
v-show="i === index"
ref="img"
:key="url"
:src="currentImg"
:style="imgStyle"
class="el-image-viewer__img"
@load="handleImgLoad"
@error="handleImgError"
@mousedown="handleMouseDown"
>
</div>
</div>
</transition>
</template>
<script lang='ts'>
import { defineComponent, computed, ref, onMounted, watch, nextTick, PropType } from 'vue'
import { rafThrottle, isFirefox } from '@element-plus/utils/util'
import { on, off } from '@element-plus/utils/dom'
import { eventKeys } from '@element-plus/utils/aria'
import { t } from '@element-plus/locale'
const Mode = {
CONTAIN: {
name: 'contain',
icon: 'el-icon-full-screen',
},
ORIGINAL: {
name: 'original',
icon: 'el-icon-c-scale-to-original',
},
}
const mousewheelEventName = isFirefox() ? 'DOMMouseScroll' : 'mousewheel'
export default defineComponent({
name: 'ElImageViewer',
props: {
urlList: {
type: Array as PropType<string[]>,
default: () => [],
},
zIndex: {
type: Number,
default: 2000,
},
onSwitch: {
type: Function,
default: () => ({}),
},
onClose: {
type: Function,
default: () => ({}),
},
initialIndex: {
type: Number,
default: 0,
},
},
setup(props) {
// init here
let _keyDownHandler = null
let _mouseWheelHandler = null
let _dragHandler = null
const loading = ref(true)
const index = ref(props.initialIndex)
const infinite = ref(true)
const wrapper = ref(null)
const img = ref(null)
const mode = ref(Mode.CONTAIN)
let transform = ref({
scale: 1,
deg: 0,
offsetX: 0,
offsetY: 0,
enableTransition: false,
})
const isSingle = computed(() => {
const { urlList } = props
return urlList.length <= 1
})
const isFirst = computed(() => {
return index.value === 0
})
const isLast = computed(() => {
return index.value === 0
})
const currentImg = computed(() => {
return props.urlList[index.value]
})
const imgStyle = computed(() => {
const { scale, deg, offsetX, offsetY, enableTransition } = transform.value
const style = {
transform: `scale(${scale}) rotate(${deg}deg)`,
transition: enableTransition ? 'transform .3s' : '',
'margin-left': `${offsetX}px`,
'margin-top': `${offsetY}px`,
}
if (mode.value.name === Mode.CONTAIN.name) {
style.maxWidth = style.maxHeight = '100%'
}
return style
})
function hide() {
deviceSupportUninstall()
props.onClose()
}
function deviceSupportInstall() {
_keyDownHandler = rafThrottle(e => {
const keyCode = e.keyCode
switch (keyCode) {
// ESC
case eventKeys.esc:
hide()
break
// SPACE
case eventKeys.space:
toggleMode()
break
// LEFT_ARROW
case eventKeys.left:
prev()
break
// UP_ARROW
case eventKeys.up:
handleActions('zoomIn')
break
// RIGHT_ARROW
case eventKeys.right:
next()
break
// DOWN_ARROW
case eventKeys.down:
handleActions('zoomOut')
break
}
})
_mouseWheelHandler = rafThrottle(e => {
const delta = e.wheelDelta ? e.wheelDelta : -e.detail
if (delta > 0) {
handleActions('zoomIn', {
zoomRate: 0.015,
enableTransition: false,
})
} else {
handleActions('zoomOut', {
zoomRate: 0.015,
enableTransition: false,
})
}
})
on(document, 'keydown', _keyDownHandler)
on(document, mousewheelEventName, _mouseWheelHandler)
}
function deviceSupportUninstall() {
off(document, 'keydown', _keyDownHandler)
off(document, mousewheelEventName, _mouseWheelHandler)
_keyDownHandler = null
_mouseWheelHandler = null
}
function handleImgLoad() {
loading.value = false
}
function handleImgError(e) {
loading.value = false
e.target.alt = t('el.image.error')
}
function handleMouseDown(e) {
if (loading.value || e.button !== 0) return
const { offsetX, offsetY } = transform.value
const startX = e.pageX
const startY = e.pageY
_dragHandler = rafThrottle(ev => {
transform.value = {
...transform.value,
offsetX: offsetX + ev.pageX - startX,
offsetY: offsetY + ev.pageY - startY,
}
})
on(document, 'mousemove', _dragHandler)
on(document, 'mouseup', () => {
off(document, 'mousemove', _dragHandler)
})
e.preventDefault()
}
function reset() {
transform.value = {
scale: 1,
deg: 0,
offsetX: 0,
offsetY: 0,
enableTransition: false,
}
}
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 && !infinite.value) return
const len = props.urlList.length
index.value = (index.value - 1 + len) % len
}
function next() {
if (isLast.value && !infinite.value) return
const len = props.urlList.length
index.value = (index.value + 1) % len
}
function handleActions(action, options = {}) {
if (loading.value) return
const { zoomRate, rotateDeg, enableTransition } = {
zoomRate: 0.2,
rotateDeg: 90,
enableTransition: true,
...options,
}
switch (action) {
case 'zoomOut':
if (transform.value.scale > 0.2) {
transform.value.scale = parseFloat((transform.value.scale - zoomRate).toFixed(3))
}
break
case 'zoomIn':
transform.value.scale = parseFloat((transform.value.scale + zoomRate).toFixed(3))
break
case 'clocelise':
transform.value.deg += rotateDeg
break
case 'anticlocelise':
transform.value.deg -= rotateDeg
break
}
transform.value.enableTransition = enableTransition
}
watch(currentImg, () => {
nextTick(() => {
const $img = img.value
if (!$img.complete) {
loading.value = true
}
})
})
watch(index, val => {
reset()
props.onSwitch(val)
})
onMounted(() => {
deviceSupportInstall()
// 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,
img,
infinite: true,
loading: false,
isSingle,
isFirst,
isLast,
currentImg,
imgStyle,
mode,
handleActions,
prev,
next,
hide,
toggleMode,
handleImgLoad,
handleImgError,
handleMouseDown,
}
},
})
</script>

View File

@ -0,0 +1,277 @@
<template>
<div ref="container" class="el-image">
<slot v-if="loading" name="placeholder">
<div class="el-image__placeholder"></div>
</slot>
<slot v-else-if="hasLoadError" name="error">
<div class="el-image__error">{{ t('el.image.error') }}</div>
</slot>
<img
v-else
class="el-image__inner"
v-bind="$attrs"
:src="src"
:style="imageStyle"
:class="{ 'el-image__inner--center': alignCenter, 'el-image__preview': preview }"
@click="clickHandler"
>
<template v-if="preview">
<image-viewer
v-if="showViewer"
:z-index="zIndex"
:initial-index="imageIndex"
:on-close="closeViewer"
:url-list="previewSrcList"
/>
</template>
</div>
</template>
<script lang='ts'>
import { defineComponent, computed, ref, onMounted, onBeforeUnmount, watch } from 'vue'
import { isString } from '@vue/shared'
import throttle from 'lodash/throttle'
import isServer from '@element-plus/utils/isServer'
import { on, off, getScrollContainer, isInContainer } from '@element-plus/utils/dom'
import { t } from '@element-plus/locale'
import ImageViewer from './image-viewer'
const isSupportObjectFit = () => document.documentElement.style.objectFit !== undefined
const isHtmlEle = e => e && e.nodeType === 1
const ObjectFit = {
NONE: 'none',
CONTAIN: 'contain',
COVER: 'cover',
FILL: 'fill',
SCALE_DOWN: 'scale-down',
}
let prevOverflow = ''
export default defineComponent({
name: 'ElImage',
components: {
ImageViewer,
},
props: {
src: {
type: String,
default: '',
},
fit: {
type: String,
default: '',
},
lazy: {
type: Boolean,
default: false,
},
scrollContainer: {
type: [String, Object],
default: null,
},
previewSrcList: {
type: Array,
default: () => [],
},
zIndex: {
type: Number,
default: 2000,
},
},
emits: ['error'],
setup(props, { emit, attrs }) {
// init here
const hasLoadError = ref(false)
const loading = ref(true)
const imgWidth = ref(false)
const imgHeight = ref(false)
const showViewer = ref(false)
const container = ref<HTMLElement | null>(null)
const show = ref(props.lazy)
let _scrollContainer = null
let _lazyLoadHandler = null
const imageStyle = computed(() => {
const { fit } = props
if (!isServer && fit) {
return isSupportObjectFit()
? { 'object-fit': fit }
: getImageStyle(fit)
}
return {}
})
const alignCenter = computed(() => {
const { fit } = props
return !isServer && !isSupportObjectFit() && fit !== ObjectFit.FILL
})
const preview = computed(() => {
const { previewSrcList } = props
return Array.isArray(previewSrcList) && previewSrcList.length > 0
})
const imageIndex = computed(() => {
const { src , previewSrcList } = props
let previewIndex = 0
const srcIndex = previewSrcList.indexOf(src)
if (srcIndex >= 0) {
previewIndex = srcIndex
}
return previewIndex
})
function getImageStyle(fit) {
const imageWidth = imgWidth.value
const imageHeight = imgHeight.value
if (!container.value) return {}
const {
clientWidth: containerWidth,
clientHeight: containerHeight,
} = container.value
if (!imageWidth || !imageHeight || !containerWidth || !containerHeight) return {}
const vertical = imageWidth / imageHeight < 1
if (fit === ObjectFit.SCALE_DOWN) {
const isSmaller = imageWidth < containerWidth && imageHeight < containerHeight
fit = isSmaller ? ObjectFit.NONE : ObjectFit.CONTAIN
}
switch (fit) {
case ObjectFit.NONE:
return { width: 'auto', height: 'auto' }
case ObjectFit.CONTAIN:
return vertical ? { width: 'auto' } : { height: 'auto' }
case ObjectFit.COVER:
return vertical ? { height: 'auto' } : { width: 'auto' }
default:
return {}
}
}
const loadImage = () => {
if (isServer) return
// reset status
loading.value = true
hasLoadError.value = false
const img = new Image()
img.onload = e => handleLoad(e, img)
img.onerror = handleError
// bind html attrs
// so it can behave consistently
Object.keys(attrs)
.forEach(key => {
const value = attrs[key]
img.setAttribute(key, value)
})
img.src = props.src
}
function handleLoad(e: Event, img: Any) {
imgWidth.value = img.width
imgHeight.value = img.height
loading.value = false
hasLoadError.value = false
}
function handleError(e: Event) {
loading.value = false
hasLoadError.value = true
emit('error', e)
}
function handleLazyLoad() {
if (isInContainer(container.value, _scrollContainer)) {
loadImage()
removeLazyLoadListener()
}
}
function addLazyLoadListener() {
if (isServer) return
const { scrollContainer } = props
if (isHtmlEle(scrollContainer)) {
_scrollContainer = scrollContainer
} else if (isString(scrollContainer) && scrollContainer !== '') {
_scrollContainer = document.querySelector(scrollContainer)
} else {
_scrollContainer = getScrollContainer(container.value)
}
if (_scrollContainer) {
_lazyLoadHandler = throttle(handleLazyLoad, 200)
on(_scrollContainer, 'scroll', _lazyLoadHandler)
setTimeout(() => handleLazyLoad(), 100)
}
}
function removeLazyLoadListener() {
if (isServer || !_scrollContainer || !_lazyLoadHandler) return
off(_scrollContainer, 'scroll', _lazyLoadHandler)
_scrollContainer = null
_lazyLoadHandler = null
}
function clickHandler() {
// don't show viewer when preview is false
if (!preview.value) {
return
}
// prevent body scroll
prevOverflow = document.body.style.overflow
document.body.style.overflow = 'hidden'
showViewer.value = true
}
function closeViewer() {
document.body.style.overflow = prevOverflow
showViewer.value = false
}
watch(() => props.src, () => {
show.value && loadImage()
})
onMounted(() => {
if (props.lazy) {
setTimeout(() => addLazyLoadListener(), 0)
} else {
loadImage()
}
})
onBeforeUnmount(() => {
props.lazy && removeLazyLoadListener()
})
return {
loading,
hasLoadError,
showViewer,
imgWidth,
imgHeight,
imageStyle,
alignCenter,
preview,
imageIndex,
clickHandler,
closeViewer,
container,
t,
}
},
})
</script>
<style>
</style>

View File

@ -113,8 +113,10 @@ export const getStyle = function(
styleName = 'cssFloat'
}
try {
const style = element.style[styleName]
if (style) return style
const computed = document.defaultView.getComputedStyle(element, '')
return element.style[styleName] || computed ? computed[styleName] : null
return computed ? computed[styleName] : ''
} catch (e) {
return element.style[styleName]
}
@ -146,13 +148,12 @@ export const isScroll = (
isVertical?: Nullable<boolean>,
): RegExpMatchArray => {
if (isServer) return
const determinedDirection = isVertical !== null || isVertical !== undefined
const determinedDirection = isVertical === null || isVertical === undefined
const overflow = determinedDirection
? isVertical
? getStyle(el, 'overflow')
: isVertical
? getStyle(el, 'overflow-y')
: getStyle(el, 'overflow-x')
: getStyle(el, 'overflow')
return overflow.match(/(scroll|auto)/)
}
@ -173,7 +174,6 @@ export const getScrollContainer = (
}
parent = parent.parentNode as HTMLElement
}
return parent
}
@ -200,7 +200,6 @@ export const isInContainer = (
} else {
containerRect = container.getBoundingClientRect()
}
return (
elRect.top < containerRect.bottom &&
elRect.bottom > containerRect.top &&