之前写过一篇文章讲图片预览。(juejin.cn/post/702209… 图片预览的实现我们已经通过canvas画图+计算长宽比实现预览,但是总体而言稍显复杂,而且里面有一些小坑。如果我们不通过canvas,是否有更好的决绝解决方式? 答案是有的。
在这个图片预览的组件中,我们花了很多的精力去得到预览图片的真实宽高。保持它不变形。这也是图片预览这个功能里最核心的要点。而这次我们还有另外一个方法可以不费吹灰之力来完成这件事。那就是借助css的maxHeight属性。
max-height 这个属性会阻止 height 属性的设置值变得比 max-height 更大。
max-height 属性用来设置给定元素的最大高度. 如果height 属性设置的高度比该属性设置的高度还大,则height 属性会失效.
借助这个特征,我们先来实现一个最简单的预览效果:
<template>
<div class="">
<View
:url="url2"
v-if="show"
@close="show=false">
</View>
<el-button @click="show=true">预览</el-button>
</div>
</template>
import View from './view.vue'
export default {
components: { View},
data() {
return {
show: false,
url:"http://121.5.169.60:8081/imageOnlineManager/images/4d41ffd0a1984e818574d8c79ed24282.jpg",//宽图片
}
}
}
//接下来是我们的预览组件的核心代码
view.vue
-------------------------
<template>
<div class="viewer">
<div
class="mask"
@click="$emit('close')"
>
</div>
<div class="img-wrap">
<i
class="el-icon-loading loading"
v-if="loading"
></i>
<img
ref="img"
class="viewer__img"
:key="url"
:src="currentImg"
@load="handleImgLoad"
@error="handleImgError"
/>
</div>
</div>
</template>
export default {
name: 'view5',
props: {
url: {
type: String,
default: ""
}
},
computed: {
currentImg() {
return this.url
},
},
watch: {
currentImg(val) {
this.$nextTick(_ => {
const $img = this.$refs.img;
if (!$img.complete) {
this.loading = true;
}
});
}
},
mounted() {
},
data() {
return {
loading: false,
isShow: false,
}
},
methods: {
handleImgLoad(e) {
this.loading = false;
},
handleImgError(e) {
this.loading = false;
e.target.alt = '加载失败';
},
loadImg() {
let imgObj = new Image()
imgObj.onload = function (e) {
}
imgObj.src = this.url
},
},
}
}
</script>
<style scoped >
.mask {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
opacity: 0.5;
background: #000;
}
.viewer {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.img-wrap {
width: 100%;
height: 100%;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.loading {
color: #fff;
}
.actions {
position: fixed;
top: 16px;
right: 30px;
width: 220px;
z-index: 9999;
color: #fff;
display: flex;
justify-content: flex-end;
}
.actions i {
display: inline-block;
margin-left: 10px;
}
.viewer__img{
max-height: 100%;
}
//特别注意:这里我们给viewer__img设置了一个maxHeight:100%
就是这个简单的属性,此时我们点击预览按钮。图片将会以正常的宽高比显示出来
不论你是长图还是宽图都正常显示。
到此,我们的工作已经完成了一半。
完整代码如下:
<template>
<div class="viewer">
<div
class="mask"
@click="$emit('close')"
></div>
<div class="actions">
<i
class="el-icon-zoom-in fs14"
@click="operate('zoomIn')"
></i>
<i
class="el-icon-zoom-out fs14"
@click="operate('zoomOut')"
></i>
<i class="el-icon-c-scale-to-original fs14"
@click="toggleMode"></i>
<i class="el-icon-refresh-left fs14"
@click="operate('rotateDY')"
></i>
<i
class="el-icon-refresh-right fs14"
@click="operate('rotateY')"
></i>
<i
class="el-icon-circle-close fs14"
@click="operate('close')"
></i>
</div>
<div class="img-wrap">
<i
class="el-icon-loading loading"
v-if="loading"
></i>
<img
ref="img"
class="viewer__img"
:key="url"
:src="currentImg"
:style="imgStyle"
@load="handleImgLoad"
@error="handleImgError"
/>
</div>
</div>
</template>
<script>
const Mode = {
CONTAIN: {
name: 'contain',
icon: 'el-icon-full-screen'
},
ORIGINAL: {
name: 'original',
icon: 'el-icon-c-scale-to-original'
}
};
export default {
name: 'HelloWorld',
props: {
url: {
type: String,
default: ""
}
},
computed: {
currentImg() {
return this.url
},
imgStyle() {
const { scale, deg, offsetX, offsetY, enableTransition } = this.transform;
const style = {
transform: `scale(${scale}) rotate(${deg}deg)`,
transition: enableTransition ? 'transform .3s' : '',
'margin-left': `${offsetX}px`,
'margin-top': `${offsetY}px`,
};
if (this.mode === Mode.CONTAIN) {
// style.maxWidth = style.maxHeight = '100%';
style.maxWidth = style.maxHeight = '100%';
}
return style;
},
},
watch: {
currentImg(val) {
this.$nextTick(_ => {
const $img = this.$refs.img;
if (!$img.complete) {
this.loading = true;
}
});
}
},
mounted() {
},
data() {
return {
loading: false,
isShow: false,
mode: Mode.CONTAIN,
transform: {
scale: 1,
deg: 0,
offsetX: 0,
offsetY: 0,
enableTransition: false
},
}
},
methods: {
handleImgLoad(e) {
this.loading = false;
},
handleImgError(e) {
this.loading = false;
e.target.alt = '加载失败';
},
loadImg() {
let imgObj = new Image()
imgObj.onload = function (e) {
}
imgObj.src = this.url
},
toggleMode() {
if (this.loading) return;
const modeNames = Object.keys(Mode);
const modeValues = Object.values(Mode);
const index = modeValues.indexOf(this.mode);
const nextIndex = (index + 1) % modeNames.length;
this.mode = Mode[modeNames[nextIndex]];
console.log("this.mode:'",this.mode)
this.reset();
},
operate(action,options={}){
if (this.loading) return;
const { zoomRate, rotateDeg, enableTransition } = {
zoomRate: 0.2,
rotateDeg: 90,
enableTransition: true,
...options
};
const { transform } = this;
this.transform.enableTransition=true;
switch (action) {
case 'zoomOut':
if (transform.scale > 0.2) {
transform.scale = parseFloat((transform.scale - zoomRate).toFixed(3));
}
break;
case 'zoomIn':
transform.scale = parseFloat((transform.scale + zoomRate).toFixed(3));
break;
case 'rotateY':
transform.deg += rotateDeg;
break;
case 'rotateDY':
transform.deg -= rotateDeg;
break;
case "close":
this.$emit("close");
break;
}
transform.enableTransition = enableTransition;
},
reset() {
this.transform = {
scale: 1,
deg: 0,
offsetX: 0,
offsetY: 0,
enableTransition: false
};
}
}
}
</script>
<style scoped >
.mask {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
opacity: 0.5;
background: #000;
}
.viewer {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.img-wrap {
width: 100%;
height: 100%;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.loading {
color: #fff;
}
.actions {
position: fixed;
top: 16px;
right: 30px;
width: 220px;
z-index: 9999;
color: #fff;
display: flex;
justify-content: flex-end;
}
.actions i {
display: inline-block;
margin-left: 10px;
}
</style>
如果看过element-ui的源码。没错。这就是它的实现思路。不得不说,实在高明。巧用css,可以事半功倍,在这个例子的基础上,我们可以在预览的时候给图片添加水印效果进行进一步拓展