业务中我们常常需要展示某张图片,因此,经常会写以下代码:
<img class="sku-img" :src="host + row.image" @error="handleLoadImgError"/>
handleLoadImgError (e) {
e.target.src = require('@/assets/image/index-logo.png') // 默认图地址
}
如果你用的是vue + element-ui,你也可以使用element-ui的Image图片组件
通过slot = error自定义一张默认图
但是无论是哪种方法,都会遇到一个问题,就是默认图片如果也加载不出来,那就会进入死循环了,因此我们可以将图片展示封装成公用方法或组件,兼容以上特殊情况,可以给我们开发大大节省时间。
使用自定义指令:
当图片src地址能正常加载,则不做处理; 当图片加载不出,则使用默认图片, 当默认图片也加载不出,则使用默认icon
因此需要定义一个检测图片是否加载出来的方法
// 检测图片是否存在
const imageIsExist = (url) => {
return new Promise((resolve) => {
let img = new Image()
img.onload = function () {
if (this.complete === true) {
resolve(true)
img = null
}
}
img.onerror = function () {
resolve(false)
img = null
}
img.src = url
})
}
接下啦就是自定义指令了:
Vue.directive('error-img', async (el, binding) => {
if (await imageIsExist(el.src)) return
const imgURL = binding.value // 获取图片地址
if (imgURL) {
const exist = await imageIsExist(imgURL)
if (exist) {
el.setAttribute('src', imgURL)
} else {
const icon = document.createElement('i')
icon.setAttribute('class', 'el-icon-picture-outline')
el.parentNode.appendChild(icon)
el.parentNode.removeChild(el)
}
} else {
el.setAttribute('src', require('@/assets/image/index-logo.png'))
}
})
require('@/assets/image/index-logo.png')为默认图片,当v-error-img没有绑定任何值时展示,<i class="el-icon-picture-outline"></i>
为element-ui图标,当默认图加载失败时替换为原来的img标签
image-view组件
思考:无论是正常图片加载还是默认图片加载都是同样的过程,即:能加载:则直接展示;不能加载出来,则展示默认的;因此,可以做成一下递归组件:
<template>
<el-image
class="img-size__inherit "
v-bind="$attrs"
v-on="$listeners"
:src="src"
>
<slot name="placeholder" slot="placeholder">
<div class="placeholder-view" >加载中...</div>
</slot>
<slot name="error" slot="error">
<!-- 默认图 -->
<image-view
class="img-size__inherit"
fit="contain"
:src="defaultSrc"
>
<!-- 默认图也没加载出的占位 -->
<div class="placeholder-view" slot="error"><i class="el-icon-picture-outline"></i></div>
</image-view>
</slot>
</el-image>
</template>
<script>
export default {
name: 'image-view',
props: {
// 图片路径
src: {
type: [String, undefined, null]
},
// 默认占位图
defaultSrc: {
type: String,
default: require('@/assets/image/index-logo.png')
}
}
}
</script>
这样的话,是否比自定义指令理解起来更简单呢
image-view组件升级
但是产品经理又有自己的想法了:能否在需要的地方移到图片有放大的效果呢
这时候我们不假思索:这个简单,只要复制一份放大的img,初始为display:none,当hover后则display:block显示出来即可。那么问题来了
-
这个hover后的大图应该放组件哪里呢
-
是否需要区分是初始图hover还是默认图hover的呢
首先hover图应该是和初始图片同级的,其次,无论是默认图还是初始图,我们都可以把hover作用在最外层的元素上,因此无需区分是初始图hover还是默认图hover
这时候我们又想到,初始图,大图加载过程都是同样的,即:能加载:则直接展示;不能加载出来,则展示默认的;因此我们也可以把大图的展示也用这个组件:
<template>
<div class="img-relative img-size__inherit">
<el-image
class="img-size__inherit "
v-bind="$attrs"
v-on="$listeners"
:src="src"
>
<slot name="placeholder" slot="placeholder">
<div class="placeholder-view" >加载中...</div>
</slot>
<slot name="error" slot="error">
<!-- 默认图 -->
<image-view
class="img-size__inherit"
fit="contain"
:hover-preview="false"
:src="defaultSrc"
>
<!-- 默认图也没加载出的占位 -->
<div class="placeholder-view" slot="error"><i class="el-icon-picture-outline"></i></div>
</image-view>
</slot>
</el-image>
<!-- hover后的大图展示图片 -->
<image-view
v-if="hoverPreview"
class="img-preview"
:hover-preview="false"
:src="src"
:style="hoverPreviewStyle"
>
</image-view>
</div>
</template>
<script>
export default {
name: 'image-view',
props: {
// 图片路径
src: {
type: [String, undefined, null]
},
// 默认占位图
defaultSrc: {
type: String,
default: require('@/assets/image/index-logo.png')
},
// 是否开启hover展示大图效果
hoverPreview: {
type: Boolean,
default: false
},
// hover后大图的样式,建议只传width
hoverPreviewStyle: {
type: [Object, String],
default: ''
}
}
}
</script>
<style lang="less" scoped>
.img-size__inherit {
width: inherit;
height: inherit;
}
.img-relative {
display: inline-block;
}
.img-relative > .img-preview {
position: fixed;
top: 50%;
left: 50%;
z-index: 1999;
display: none;
width: auto;
max-width: 60vw;
height: auto;
max-height: 60vh;
background: #fff;
transform: translate(-50%, -50%);
}
.img-relative:hover > .img-preview {
display: block;
}
.placeholder-view {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background: #eee;
}
</style>
image-view组件完善
如果说需要通过外界对图片路径做处理的话,还可以把对路径处理的方法通过props传入,因此,最终优化版的image-view组件为:
<template>
<div class="img-relative img-size__inherit">
<el-image
class="img-size__inherit "
v-bind="$attrs"
v-on="$listeners"
:src="usePathHandler ? pathHandler(src) : src"
>
<slot name="placeholder" slot="placeholder">
<div class="placeholder-view" >加载中...</div>
</slot>
<slot name="error" slot="error">
<!-- 默认图 -->
<image-view
class="img-size__inherit"
fit="contain"
:usePathHandler="false"
:src="defaultSrc"
>
<!-- 默认图也没加载出的占位 -->
<div class="placeholder-view" slot="error"><i class="el-icon-picture-outline"></i></div>
</image-view>
</slot>
</el-image>
<!-- hover后的大图展示图片 -->
<image-view
v-if="hoverPreview"
class="img-preview"
:src="src"
:style="hoverPreviewStyle"
:usePathHandler="usePathHandler"
:defaultSrc="defaultSrc"
:pathHandler="pathHandler"
>
</image-view>
</div>
</template>
<script>
import ImageView from './ImageView.vue'
import { imgPathHandler } from '@/utils/util' // 图片路径处理的默认方法
/**
* 图片视图(拥有默认占位图)
* 本组件为对 el-image 的封装, 可传入任意 el-image 的 props 和 listener
*
* slot[ placeholder ]: 加载占位
* slot[ error ]: 错误占位
*/
export default {
name: 'image-view',
components: { ImageView },
props: {
// 图片路径
src: {
type: [String, undefined, null]
},
// 默认占位图
defaultSrc: {
type: String,
default: require('@/assets/image/index-logo.png')
},
// 是否使用路径处理器
usePathHandler: {
type: Boolean,
default: true
},
// 路径处理器
pathHandler: {
type: Function,
default: imgPathHandler
},
// 是否开启hover展示大图效果
hoverPreview: {
type: Boolean,
default: false
},
// hover后大图的样式,建议只传width
hoverPreviewStyle: {
type: [Object, String],
default: ''
}
}
}
</script>
<style lang="less" scoped>
.img-size__inherit {
width: inherit;
height: inherit;
}
.img-relative {
display: inline-block;
}
.img-relative > .img-preview {
position: fixed;
top: 50%;
left: 50%;
z-index: 1999;
display: none;
width: auto;
max-width: 60vw;
height: auto;
max-height: 60vh;
background: #fff;
transform: translate(-50%, -50%);
}
.img-relative:hover > .img-preview {
display: block;
}
.placeholder-view {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background: #eee;
}
</style>
实现的效果如下: