前段时间接了新需求,需要对图片进行预览以及文件的下载,没找到合适的组件来实现这需求,最终自己写了该组件来实现此功能,该组件可预览图片、视频,均可下载,有旋转、放大/缩小功能。【该组件没有用到第三方插件实现】
如果小伙伴们需要用到,可根据自己的需求进行修改
效果图:(背景颜色值,可自行修改)
用法:
HTML片段:
<template>
<div
id="picture-viewer"
:style="maskContainer"
@mousewheel="mousewheel"
>
<!-- 头部 -->
<flexbox class="perview-header">
<div class="left">{{ imgIndex + 1 }} / {{ imgLength }}</div>
<div class="center">
{{ bigImgName.slice(0, bigImgName.indexOf(".")) }}
</div>
<!-- @keyup.esc="closeViewer" -->
<img
class="close"
src="./img/pre_close.png"
@click="closeViewer"
/>
</flexbox>
<!-- 图片容器 -->
<div ref="imgContainer" :style="imgContainer" class="imgContainer">
<img
v-if="bigShowType.isImage"
ref="bigImg"
:src="bigImageUrl"
:style="bigImgStyle"
alt=""
/>
<flexbox
v-if="!bigShowType.isImage && !isOpen"
class="file-show"
direction="column "
justify="space-evenly"
>
<div class="file-icon" @click="fileHandles(bigShowType)" v-if="!isOpen">
<img :src="bigShowType.icon" />
</div>
<div class="file-handle" v-if="!isOpen">
<el-button
type="primary"
plain
@click.native="fileHandle('download')"
>
下载
</el-button>
</div>
</flexbox>
<!-- tips -->
<transition name="fade">
<div v-show="showTips" class="tips">{{ tipsText }}</div>
</transition>
</div>
<div class="fixedHandle">
<!-- 操作按钮 -->
<flexbox v-if="bigShowType.isImage" class="handleContainer">
<img src="./img/pre_max.png" @click="enlarge" />
<img src="./img/pre_min.png" @click="reduce" />
<img
style="padding: 4.5px"
src="./img/pre_rotate.png"
@click="rotate"
/>
<img
src="./img/pre_down.png"
@click="downloadImg(bigImageUrl, bigImgName)"
/>
</flexbox>
<!-- 缩略图容器 -->
<div v-if="imgLength > 1" class="thumbnailContainer">
<ul>
<li
v-for="(item, index) in imgData"
ref="thumbnailItem"
:key="index"
@click="switchImgUrl(index, $event)"
>
<img v-if="isShowImage(item.url)" :src="item.url" alt="" />
<img
v-if="!isShowImage(item.url)"
:src="getFileTypeIconWithSuffix(item.url)"
alt=""
/>
</li>
</ul>
</div>
</div>
<!-- 左边箭头 -->
<div
class="leftArrowCon"
:style="{ zIndex: isOpen ? -1 : '' }"
@click="handlePrev"
@mouseenter="enterLeft"
@mouseout="outLeft"
>
<img
v-show="leftArrowShow"
class="leftArrow"
src="./img/pre_left.png"
@click="enlarge"
/>
</div>
<!-- 右边箭头 -->
<div
class="rightArrowCon"
:style="{ zIndex: isOpen ? -1 : '' }"
@click="handleNext"
@mouseenter="enterRight"
@mouseout="outRight"
>
<img
v-show="rightArrowShow"
class="rightArrow"
src="./img/pre_right.png"
/>
</div>
<video
:src="url"
v-if="isOpen"
controls
style="
position: absolute;
top: 177px;
left: 50%;
width: 750px;
margin-left: -335px;
"
></video>
</div>
</template>
js片段:
<script>
export default {
name: "Vue2PictureViewer",//vue2 图片预览
props: {
imgData: {
type: Array,
default: () => {
return [];
}
},
background: {
type: String,
default: "rgba(0,0,0,0.4)"
},
// 选择的索引
selectIndex: {
type: Number,
default: -1
},
appendToBody: {
type: Boolean,
default: false
}
},
data() {
return {
isOpen: false,
// 默认不显示左右切换箭头
leftArrowShow: false,
rightArrowShow: false,
// 图片容器数据
rotateDeg: 0,
bigImageUrl: "",
bigShowType: { isImage: true, icon: "" }, // 不是图片的时候 展示 icon
bigImgName: "",
imgLength: 0,
imgIndex: 0,
showTips: false,
tipsText: "",
bigImgConTrnWidth: "",
bigImgConTrnHeight: "",
maskContainer: {
width: "100%",
height: "100%",
background: this.background,
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0
},
imgContainer: {
width: "auto",
height: "auto",
position: "absolute",
top: "50%",
left: "50%",
"z-index": 100,
transform: "translate(-50%, -50%)"
},
bigImgStyle: {
display: "block",
width: "80px",
height: "80px",
position: "absolute",
top: 50 + "%",
left: 50 + "%",
marginLeft: "",
marginTop: "",
userSelect: "none"
},
url: ""
};
},
mounted() {
if (!this.appendToBody) {
document
.getElementById("picture-viewer")
.addEventListener("click", (e) => {
e.stopPropagation();
});
}
if (this.appendToBody) {
document.body.appendChild(this.$el);
}
this.imgLength = this.imgData.length;
this.imgIndex = this.selectIndex;
this.$nextTick(() => {
this.bigImageUrl = this.imgData[this.imgIndex].url;
this.getShowTypeInfo(this.bigImageUrl);
this.bigImgName = this.imgData[this.imgIndex].name;
if (this.imgLength > 1) {
// 大于1的时候才会展示缩略图
var item = this.$refs.thumbnailItem;
item[this.imgIndex].className = "borderActive";
}
});
var self = this;
this.$refs.bigImg.onload = () => {
self.init();
};
this.maskContainer["z-index"] = 999999;
},
beforeDestroy() {
if (document.getElementById("picture-viewer")) {
document
.getElementById("picture-viewer")
.removeEventListener("click", (e) => {
e.stopPropagation();
});
}
if (this.appendToBody && this.$el && this.$el.parentNode) {
this.$el.parentNode.removeChild(this.$el);
}
},
methods: {
mousewheel(e) {
let _this = this;
let ev = e || window.event;
// ;
if (ev.wheelDelta) {
if (ev.wheelDelta > 0) {
//当滑轮向上滚动时
_this.enlarge();
}
if (ev.wheelDelta < 0) {
_this.reduce();
}
} else if (ev.detail) {
if (ev.detail > 0) {
//当滑轮向上滚动时
_this.enlarge();
}
if (ev.detail < 0) {
_this.reduce();
}
}
},
// init
init() {
const screenW =
document.documentElement.offsetWidth || document.body.offsetWidth;
const screenH =
document.documentElement.scrollHeight || document.body.scrollHeight;
this.$nextTick(function () {
const ratio = [0.1, 0.2, 0.3, 0.4, 0.5, 0.7, 0.8, 0.9];
for (const item of ratio) {
if (
this.$refs.bigImg.naturalWidth * item < screenW &&
this.$refs.bigImg.naturalHeight * item < screenH - 200
) {
this.bigImgConTrnWidth = this.$refs.bigImg.naturalWidth * item;
this.bigImgConTrnHeight = this.$refs.bigImg.naturalHeight * item;
this.imgContainer.width = this.bigImgConTrnWidth + "px";
this.imgContainer.height = this.bigImgConTrnHeight + "px";
this.bigImgStyle.width = this.bigImgConTrnWidth + "px";
this.bigImgStyle.height = this.bigImgConTrnHeight + "px";
this.bigImgStyle.marginLeft = -(this.bigImgConTrnWidth / 2) + "px";
this.bigImgStyle.marginTop = -(this.bigImgConTrnHeight / 2) + "px";
}
}
});
},
// rotate init
rotateInit() {
const screenH =
document.documentElement.scrollHeight || document.body.scrollHeight;
const ratio = [0.1, 0.2, 0.3, 0.4, 0.5, 0.7, 0.8, 0.9];
for (const item of ratio) {
if (this.$refs.bigImg.naturalWidth * item < screenH - 160) {
this.bigImgConTrnWidth = this.$refs.bigImg.naturalWidth * item;
this.bigImgConTrnHeight = this.$refs.bigImg.naturalHeight * item;
this.imgContainer.width = this.bigImgConTrnWidth + "px";
this.imgContainer.height = this.bigImgConTrnHeight + "px";
this.bigImgStyle.width = this.bigImgConTrnWidth + "px";
this.bigImgStyle.height = this.bigImgConTrnHeight + "px";
this.bigImgStyle.marginLeft = -(this.bigImgConTrnWidth / 2) + "px";
this.bigImgStyle.marginTop = -(this.bigImgConTrnHeight / 2) + "px";
}
}
},
// 放大
enlarge() {
this.$nextTick(function () {
const screenW =
document.documentElement.offsetWidth || document.body.offsetWidth;
const screenH =
document.documentElement.scrollHeight || document.body.scrollHeight;
if (
(this.$refs.bigImg.offsetWidth >= this.$refs.bigImg.offsetHeight &&
this.$refs.bigImg.offsetHeight * 2 < screenH * 2) ||
(this.$refs.bigImg.offsetHeight >= this.$refs.bigImg.offsetWidth &&
this.$refs.bigImg.offsetWidth * 2 < screenW * 2)
) {
this.$refs.bigImg.style.width =
this.$refs.bigImg.offsetWidth * 1.3 + "px";
this.$refs.bigImg.style.height =
this.$refs.bigImg.offsetHeight * 1.3 + "px";
this.$refs.bigImg.style.left = "50%";
this.$refs.bigImg.style.top = "50%";
this.bigImgStyle.marginLeft =
-this.$refs.bigImg.offsetWidth / 2 + "px";
this.bigImgStyle.marginTop =
-this.$refs.bigImg.offsetHeight / 2 + "px";
}
});
},
// 缩小
reduce() {
if (this.$refs.bigImg.offsetWidth > 80) {
/**
* clientWidth = width + padding
offsetWidth = width + padding + border */
this.$refs.bigImg.style.width =
this.$refs.bigImg.offsetWidth * 0.7 + "px";
this.$refs.bigImg.style.height =
this.$refs.bigImg.offsetHeight * 0.7 + "px";
this.$refs.bigImg.style.left = "50%";
this.$refs.bigImg.style.top = "50%";
this.bigImgStyle.marginLeft = -this.$refs.bigImg.offsetWidth / 2 + "px";
this.bigImgStyle.marginTop = -this.$refs.bigImg.offsetHeight / 2 + "px";
}
},
// 旋转
rotate() {
const numberAngle = [45,90,120,160,180,230,270,310,360]
const numberAngleSelect = numberAngle[this.rotateDeg] ?? 90
if(this.rotateDeg === numberAngle.length){
this.$refs.bigImg.style.transform =`rotate(${numberAngleSelect}deg)`;
this.init();
this.rotateDeg = 0;
}else{
this.$refs.bigImg.style.transform =`rotate(${numberAngleSelect}deg)`;
this.rotateInit();
this.rotateDeg++;
}
},
// 点击缩略图切换图片
switchImgUrl(num, e) {
this.isOpen = false;
var item = this.$refs.thumbnailItem;
item.forEach(function (i) {
i.className = "";
});
this.imgIndex = num;
this.bigImageUrl = this.imgData[num].url;
this.getShowTypeInfo(this.bigImageUrl);
this.bigImgName = this.imgData[num].name;
e.currentTarget.className = "borderActive";
if (this.bigShowType.isImage) {
this.init();
}
},
// 切换到上一张
handlePrev() {
this.isOpen = false;
if (this.imgIndex <= 0) {
this.tips("已经是第一张了!");
this.imgIndex = 0;
} else {
if (this.$refs.bigImg) {
this.$refs.bigImg.style.transform = "rotate(0deg)";
this.rotateDeg = 0;
}
this.imgIndex--;
this.bigImageUrl = this.imgData[this.imgIndex].url;
this.getShowTypeInfo(this.bigImageUrl);
this.bigImgName = this.imgData[this.imgIndex].name;
var item = this.$refs.thumbnailItem;
item.forEach(function (i) {
i.className = "";
});
item[this.imgIndex].className = "borderActive";
if (this.bigShowType.isImage) {
this.init();
}
}
},
// 切换到下一张
handleNext() {
this.isOpen = false;
if (this.imgIndex + 1 >= this.imgData.length) {
this.tips("已经是最后一张了!");
} else {
if (this.$refs.bigImg) {
this.$refs.bigImg.style.transform = "rotate(0deg)";
this.rotateDeg = 0;
}
this.imgIndex++;
this.bigImageUrl = this.imgData[this.imgIndex].url;
this.getShowTypeInfo(this.bigImageUrl);
this.bigImgName = this.imgData[this.imgIndex].name;
var item = this.$refs.thumbnailItem;
item.forEach(function (i) {
i.className = "";
});
item[this.imgIndex].className = "borderActive";
if (this.bigShowType.isImage) {
this.init();
}
}
},
// 提示框
tips(msg) {
this.showTips = true;
this.tipsText = msg;
const _this = this;
setTimeout(function () {
_this.showTips = false;
}, 1000);
},
// 下载图片
downloadImg(data, filename) {
this.downloadImage(data, filename);
},
// 鼠标左移
enterLeft() {
this.leftArrowShow = true;
},
outLeft() {
this.leftArrowShow = false;
},
// 鼠标右移
enterRight() {
this.rightArrowShow = true;
},
outRight() {
this.rightArrowShow = false;
},
// 关闭查看器
closeViewer(e) {
this.$emit("close-viewer");
},
fileHandles(item) {
let _this = this;
const temps = this.bigImageUrl ? this.bigImageUrl.split(".") : [];
var ext = "";
if (temps.length > 0) {
ext = temps[temps.length - 1];
} else {
ext = "";
}
let moviesArray = ["mp4", "3gp", "m4v", "avi", "mkv", "webm"];
if (moviesArray.includes(ext)) {
this.isOpen = !this.isOpen;
this.url = this.bigImageUrl;
} else {
this.fileHandle();
}
},
/** 附件逻辑 */
fileHandle(type) {
var a = document.createElement("a");
a.href = this.bigImageUrl;
a.download = this.bigImgName ? this.bigImgName : "文件";
a.target = "_black";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
},
getShowTypeInfo(url) {
const temps = url ? url.split(".") : [];
var ext = "";
if (temps.length > 0) {
ext = temps[temps.length - 1];
} else {
ext = "";
}
var icon = "";
var isImage = true;
if (this.arrayContain(["jpg", "png", "gif", "jpeg"], ext)) {
isImage = true;
icon = require("@/assets/img/file_img.png");
} else if (this.arrayContain(["mp4", "mp3", "avi"], ext)) {
isImage = false;
icon = require("@/assets/home_img/MP4.png");
} else if (this.arrayContain(["xlsx", "xls", "XLSX", "XLS"], ext)) {
isImage = false;
icon = require("@/assets/home_img/XLS.png");
} else if (this.arrayContain(["doc", "docx", "DOC", "DOCX"], ext)) {
isImage = false;
icon = require("@/assets/home_img/DOC.png");
} else if (this.arrayContain(["rar", "zip"], ext)) {
isImage = false;
icon = require("@/assets/home_img/zip_rar.png");
} else if (ext === "pdf") {
isImage = false;
icon = require("@/assets/home_img/ppt.png");
} else if (ext === "ppt" || ext === "pptx") {
isImage = false;
icon = require("@/assets/home_img/ppt.png");
} else if (this.arrayContain(["txt", "text"], ext)) {
isImage = false;
icon = require("@/assets/home_img/txt.png");
} else {
isImage = false;
icon = require("@/assets/img/file_unknown.png");
}
this.bigShowType = { isImage: isImage, icon: icon };
},
getFileTypeIconWithSuffix(url) {
const temps = url ? url.split(".") : [];
var ext = "";
if (temps.length > 0) {
ext = temps[temps.length - 1];
} else {
ext = "";
}
if (this.arrayContain(["jpg", "png", "gif", "jpeg"], ext)) {
return require("@/assets/img/file_img.png");
} else if (this.arrayContain(["mp4", "mp3", "avi"], ext)) {
return require("@/assets/home_img/MP4.png");
} else if (this.arrayContain(["xlsx", "xls", "XLSX", "XLS"], ext)) {
return require("@/assets/home_img/XLS.png");
} else if (this.arrayContain(["doc", "docx", "DOC", "DOCX"], ext)) {
return require("@/assets/home_img/DOC.png");
} else if (this.arrayContain(["rar", "zip"], ext)) {
return require("@/assets/home_img/zip_rar.png");
} else if (ext === "pdf") {
return require("@/assets/home_img/ppt.png");
} else if (ext === "ppt" || ext === "pptx") {
return require("@/assets/home_img/ppt.png");
} else if (this.arrayContain(["txt", "text"], ext)) {
return require("@/assets/home_img/txt.png");
}
return require("@/assets/img/file_unknown.png");
},
isShowImage(url) {
const temps = url ? url.split(".") : [];
var ext = "";
if (temps.length > 0) {
ext = temps[temps.length - 1];
} else {
ext = "";
}
if (this.arrayContain(["jpg", "png", "gif", "jpeg"], ext)) {
return true;
}
return false;
},
arrayContain(array, string) {
return array.some((item) => {
return item === string;
});
},
downloadImage(data, filename) {
var httpindex = data.indexOf("http");
let _this = this;
if (httpindex === 0) {
const image = new Image();
// 解决跨域 canvas 污染问题
image.setAttribute("crossOrigin", "Anonymous");
image.onload = function () {
let canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
let context = canvas.getContext("2d");
context.drawImage(image, 0, 0, image.width, image.height);
let dataURL = canvas.toDataURL("image/png");
//
//
// 生成一个 a 标签
let a = document.createElement("a");
// 创建一个点击事件
let event = new MouseEvent("click");
// 将 a 的 download 属性设置为我们想要下载的图片的名称,若 name 不存在则使用'图片'作为默认名称
a.download = filename || "图片";
// 将生成的 URL 设置为 a.href 属性
let blob = _this.dataURLtoBlob(dataURL);
a.href = URL.createObjectURL(blob);
// 触发 a 的点击事件
a.dispatchEvent(event);
};
image.src = data;
} else {
// 生成一个 a 标签
let a = document.createElement("a");
// 创建一个点击事件
let event = new MouseEvent("click");
a.download = filename || "图片";
// 将生成的 URL 设置为 a.href 属性
a.href = data;
// 触发 a 的点击事件
a.dispatchEvent(event);
}
},
dataURLtoBlob(dataurl) {
// eslint-disable-next-line one-var
var arr = dataurl.split(","),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], {
type: mime
});
}
},
};
</script>
css片段:
<style lang="scss" scoped>
.perview-header {
width: 100%;
height: 40px;
background: rgba(0, 0, 0, 0.6);
color: rgba(255, 255, 255, 0.8);
line-height: 40px;
position: fixed;
top: 0;
left: 0;
z-index: 6000;
padding: 10px;
.left {
flex-shrink: 0;
font-size: 14px;
}
.center {
text-align: center;
flex: 1;
padding: 0 20px;
}
.close {
display: block;
padding: 8px;
width: 40px;
height: 40px;
cursor: pointer;
}
}
.leftArrowCon {
width: 30%;
height: calc(100% - 40px);
background: transparent;
position: absolute;
top: 40px;
left: 0;
z-index: 98;
cursor: pointer;
}
.rightArrowCon {
width: 30%;
height: calc(100% - 40px);
background: transparent;
position: absolute;
top: 40px;
right: 0;
z-index: 99;
cursor: pointer;
}
.leftArrow {
position: absolute;
top: 50%;
left: 30px;
margin-top: -60px;
transition: all 0.5s;
width: 50px;
height: 50px;
pointer-events: none;
}
.rightArrow {
position: absolute;
top: 50%;
right: 30px;
margin-top: -60px;
width: 50px;
height: 50px;
transition: all 0.5s;
pointer-events: none;
}
.imgContainer .tips {
background: rgba(0, 0, 0, 0.7);
color: #fff;
text-align: center;
line-height: 40px;
position: absolute;
left: 50%;
top: 50%;
min-width: 150px;
margin-left: -60px;
margin-top: -20px;
border-radius: 6px;
padding: 4px 4px;
font-size: 14px;
}
.fixedHandle {
width: 800px;
height: 140px;
position: fixed;
left: 50%;
bottom: 0;
margin-left: -400px;
overflow: hidden;
z-index: 100;
}
.handleContainer {
width: 210px;
height: 40px;
background: rgba(0, 0, 0, 0.6);
line-height: 40px;
border-radius: 20px;
position: absolute;
left: 50%;
bottom: 100px;
padding: 0 14px;
margin-left: -100px;
img {
display: block;
width: 40px;
height: 40px;
padding: 8px;
margin: 0 2px;
cursor: pointer;
}
}
.handleItem {
width: 28px;
height: 28px;
color: white;
}
ul {
padding: 0;
margin: 0;
}
ul li {
list-style: none;
display: inline-block;
width: 30px;
height: 30px;
margin-left: 20px;
cursor: pointer;
}
.thumbnailContainer {
max-width: 800px;
background: rgba(255, 255, 255, 0.7);
position: absolute;
left: 50%;
bottom: 0;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
transform: translate(-50%, 0%);
overflow-x: auto;
overflow-y: hidden;
}
.thumbnailContainer ul {
padding-top: 10px;
padding-bottom: 10px;
text-align: center;
white-space: nowrap;
}
.thumbnailContainer ul li {
display: inline-block;
width: 38px;
height: 38px;
box-sizing: content-box;
margin-left: 10px;
user-select: none;
}
.thumbnailContainer ul li:last-child {
margin-right: 10px;
}
.thumbnailContainer ul li img {
display: inline-block;
width: 38px;
height: 38px;
border-radius: 3px;
box-sizing: content-box;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 1s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
/* 添加border */
.borderActive {
box-shadow: 0px 4px 7px 0px rgb(241, 140, 112);
}
/* 修改原生的滚动条 */
::-webkit-scrollbar {
/* 血槽宽度 */
width: 8px;
height: 8px;
}
::-webkit-scrollbar-thumb {
/* 拖动条 */
background: rgba(0, 0, 0, 0.3);
border-radius: 6px;
}
::-webkit-scrollbar-track {
/* 背景槽 */
background: #ddd;
border-radius: 6px;
}
/** 文件展示*/
.file-show {
position: absolute;
top: 60%;
left: 50%;
width: 450px;
height: 260px;
margin-top: -150px;
margin-left: -225px;
background-color: white;
border-radius: 10px;
padding: 0 80px;
.file-icon {
// flex: 1;
cursor: pointer;
img {
display: block;
width: 60px;
&:hover {
transform: rotateY(180deg);
// transform: scale(0.5);
// transition: all 0.2s ease-in;
}
}
}
.file-handle {
button {
display: block;
width: 120px;
margin-left: 0;
margin-right: 0;
height: 34px;
}
button:first-child {
margin-bottom: 20px;
}
}
}
</style>