安装/Installation
npm i vue3-image-viewer
或者/or
yarn add vue3-image-viewer
引入/Import
import { Image } from 'vue3-image-viewer';
import 'vue3-image-viewer/dist/style.css';
使用示例/Example
<template>
<div class="vue3-image-viewer">
<section class="image">
<div class="image-wrap" v-for="(item, index) in images" :key="item.url">
<Image
:base="base"
:src="item.url"
:token="token"
:aspectRatio="3 / 4"
fit="contain"
lazy
hideOnClickModal
:initialIndex="index"
preview
:images="images"
loop
:preload="[-2, 2]"
width="220px"
alt="图片组件测试"
bgColor="lightpink"
@error="imageError"
@load="imageLoad"
@close="imageClose"
@switch="ImageSwitch"
>
<template v-slot:footer>footer</template>
<template v-slot:loading>LOADING</template>
<template v-slot:error>ERROR</template>
</Image>
</div>
</section>
<section class="viewer">
<div class="preview">
<div class="item" v-for="(item, index) in current.marks" :key="index">
<Viewer
:base="base"
:token="token"
:image="current"
:mark="item"
focus
>
<template v-slot:loading>LOADING</template>
<template v-slot:error>ERROR</template>
</Viewer>
</div>
</div>
<!-- 自定义 -->
<div class="tools">
<div @click="toolBtn('fit')">适应窗口</div>
<div @click="toolBtn('prev')">上一个</div>
<div @click="toolBtn('next')">下一个</div>
<div @click="toolBtn('reset')">重置</div>
</div>
<div class="viewer-wrap">
<!-- 如果传入了markList则不取images中的marks -->
<Viewer
ref="viewer"
:base="base"
:token="token"
:scaleMax="20"
:scaleMin="0.1"
:scaleStep="1.2"
:initialScale="1"
:imageList="images"
:markList="current.marks"
:initialIndex="currentIndex"
loop
navigation
toolbar
drawable
deleteable
:keyboard="['A', 'D', 'Space', 'Esc']"
@error="onError"
@load="onLoad"
@switch="onSwitch"
@drawEnd="drawEnd"
@resize="onResize"
@delete="onDelete"
@zoom="onZoom"
>
<template v-slot:loading>LOADING</template>
<template v-slot:error>ERROR</template>
</Viewer>
</div>
</section>
</div>
</template>
<script setup lang="ts">
import { Image, Viewer } from 'vue3-image-viewer';
import { reactive, ref, computed } from 'vue';
const viewer = ref(null);
const currentIndex = ref(0);
const base = 'https://cube.elemecdn.com/';
const token = 'token';
const images = reactive([
{
title: '1',
desc: 'A',
url: 'a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
thumbnail: 'a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
marks: [
{
id: '1',
left: 0,
top: 0,
width: 100,
height: 100,
},
{
id: '2',
left: 200,
top: 200,
width: 50,
height: 100,
},
],
},
{
title: '2',
desc: 'B',
url: 'd/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg',
thumbnail: 'd/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg',
},
{
title: '3',
desc: 'C',
url: '1/8e/aeffeb4de74e2fde4bd74fc7b4486jpeg.jpeg1',
thumbnail:
'1/8e/aeffeb4de74e2fde4bd74fc7b4486jpeg.jpeg1',
},
{
title: '4',
desc: 'B',
url: 'a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
thumbnail: 'a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
},
{
title: '5',
desc: 'B',
url: 'a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
thumbnail: 'a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
},
{
title: '6',
desc: 'B',
url: 'a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
thumbnail: 'a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
},
{
title: '7',
desc: 'B',
url: 'a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
thumbnail: 'a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
},
{
title: '8',
desc: 'B',
url: '6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg',
thumbnail: '6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg',
},
{
title: '9',
desc: 'B',
url: '9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg',
thumbnail: '9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg',
},
{
title: '10',
desc: 'B',
url: '3/28/bbf893f792f03a54408b3b7a7ebf0jpeg.jpeg',
thumbnail: '3/28/bbf893f792f03a54408b3b7a7ebf0jpeg.jpeg',
},
]);
const current = computed(() => {
return images[currentIndex.value];
});
function onError(err) {
// console.log(err);
}
function onLoad(ev) {
// console.log(ev);
}
function onSwitch(index) {
// console.log(index);
currentIndex.value = index;
}
function drawEnd(item) {
const { left, top, width, height } = item;
if (!current.value.marks) {
current.value.marks = [];
}
current.value.marks.push({
id: current.value.marks.length + 1 + '',
left,
top,
width,
height,
});
}
function onResize({ target, detail }) {
const { left, top, width, height } = detail;
console.log(left, top, width, height);
const mark = { ...target };
mark.left = left;
mark.top = top;
mark.width = width;
mark.height = height;
const idx = current.value.marks.indexOf(target);
if (idx > -1) {
current.value.marks.splice(idx, 1, mark);
}
}
function onDelete({ target }) {
const idx = current.value.marks.indexOf(target);
if (idx > -1) {
current.value.marks.splice(idx, 1);
}
}
function onZoom(zoom) {
// console.log(zoom);
}
function imageError(err) {
// console.log(err);
}
function imageLoad(ev) {
// console.log(ev);
}
function imageClose() {
// console.log('close');
}
function ImageSwitch(idx) {
// console.log(idx);
}
function toolBtn(type) {
if (!viewer.value) {
return;
}
if (type === 'fit') {
viewer.value.toggleFit();
}
if (type === 'next') {
viewer.value.next();
}
if (type === 'prev') {
viewer.value.prev();
}
if (type === 'reset') {
viewer.value.reset();
}
}
</script>
<style lang="less" scoped>
.vue3-image-viewer {
.image {
width: 750px;
margin: 20px auto;
padding: 0 10px;
max-height: 200px;
overflow-y: auto;
.image-wrap {
float: left;
margin: 10px;
}
}
.viewer {
margin-bottom: 50px;
.preview {
display: flex;
// flex-wrap: wrap;
.item {
width: 200px;
margin: 10px;
}
}
.tools {
width: 400px;
margin: 20px auto;
display: flex;
justify-content: space-around;
div {
cursor: pointer;
height: 22px;
line-height: 22px;
padding: 0 10px;
border-radius: 2px;
background-color: #333;
color: #fff;
}
}
padding: 0 20px;
.viewer-wrap {
width: 100%;
height: 500px;
}
}
}
</style>
Image 组件说明
传参/Props
参数 |
说明 |
类型 |
默认值 |
base |
地址前缀 |
string |
"" |
src |
图片地址 |
string |
"" |
token |
token 参数 |
string |
"" |
aspectRatio |
固定宽高比 |
number |
0 |
fit |
object-fit |
string |
"fill" |
lazy |
懒加载 |
boolean |
false |
hideOnClickModal |
点击遮罩关闭预览 |
boolean |
false |
initialIndex |
在预览数组中的位置 |
number |
0 |
preview |
是否开启预览 |
boolean |
false |
images |
预览列表 |
array |
[] |
loop |
开启循环 |
boolean |
false |
preload |
预览预加载 |
array or boolean |
[0,2] |
width |
盒子宽度 |
string |
"" |
height |
盒子高度 |
string |
"" |
alt |
alt |
string |
"" |
referrerPolicy |
referrerPolicy |
string |
"" |
bgColor |
背景颜色 |
string |
"black" |
参数说明/Props Explanation
- src: 如果 src 是绝对地址,则 base, token 不生效,如果是相对地址,返回 base+src+?token="abc"
- width&height: 可以设置固定 px 值或者百分比值,等同于原生 css 属性
- aspectRatio: 如果设置了 aspectRatio,则高度由宽度和 aspectRatio 决定,height 不再生效
- preload: 如果设置为 false 则关闭预加载,如果设置为[1,2],则在查看当前图片的同时会同时加载左边 1 张右边 2 张图片
组件事件/Event
- error: 图片加载出错事件/ Same as native error
- load: 图片加载完成事件/ Same as native load
- close: 关闭预览事件 / Emit when close the preview
- switch: 切换预览图片事件,返回下标 / Emit when switch the preview, return current index
支持插槽/Slot
- error:加载出错占位/ Triggers when image load failed
- loading:加载中占位/ Triggers when image is loading
- footer:图片底部占位/ Placeholder on the bottom
Viewer 组件说明
传参/Props
参数 |
说明 |
类型 |
默认值 |
base |
地址前缀 |
string |
"" |
token |
token 参数 |
string |
"" |
scaleMax |
最大缩放倍数 |
number |
15 |
scaleMin |
最小缩放倍数 |
number |
1 |
scaleStep |
缩放步进 |
number |
1.1 |
initialScale |
初始缩放倍数 |
number |
1 |
imageList |
图片列表 |
array |
[] |
markList |
标记框列表 |
array |
[] |
initialIndex |
初始展示图片下标 |
number |
0 |
loop |
开启循环 |
boolean |
0 |
navigation |
底部预览条 |
boolean |
false |
toolbar |
工具栏 |
boolean |
false |
keyboard |
开启快捷键 |
array |
['A', 'D'] |
fit |
object-fit |
string |
"contain" |
preload |
图片预加载 |
array or boolean |
[0,2] |
fixed |
禁止缩放 |
boolean |
false |
focus |
锁定标记框的展示模式 |
boolean |
false |
drawable |
是否开启标记功能 |
boolean |
false |
deleteable |
是否显示移除标记框的按钮 |
boolean |
false |
image |
单张图片 |
object |
null |
mark |
单个标记框 |
object |
null |
参数说明/Props Explanation
- scaleStep: 按倍数缩进
- keyboard: 支持的快捷键有上一项:
A
,下一项: D
,锁定标记框: Space
,取消框图锁定: Esc
- imageList: 数据结构请参考示例代码,优先使用imageList,其次使用image
- markList: 数据结构请参考示例代码,标记框优先使用markList,其次是image的marks字段,再次是mark字段
- focus: focus模式下,不可缩放,锁定标记框,不可调整大小
- drawable: 开启绘制标记框的功能
组件事件/Event
- error: 图片加载出错事件/ Same as native error
- load: 图片加载完成事件/ Same as native load
- switch: 切换预览图片事件,返回下标 / Emit when switch the preview, return current index
- drawEnd: 绘制结束时的事件,返回标记框图的关键信息
- resize: 调整大小结束时的事件,返回resize的关键信息
- delete: 点击移除按钮时触发的事件,返回点击的对象
- zoom: 缩放时触发的事件,返回当前的缩放倍数
组件方法/Methods
- clear: 清除绘制的标记框图
- switch: 切换图片,传入图片下标值
- toggleFit: 切换contain和cover模式
- unFocusMark: 取消框图锁定
- focusMark: 锁定框图
- reset: 重置图片状态(缩放、移动等)
- download: 下载图片
- zoomOut: 放大
- zoomIn: 缩小
- prev: 上一张
- next: 下一张
支持插槽/Slot
- error: 加载出错占位/ Triggers when image load failed
- loading: 加载中占位/ Triggers when image is loading
- toolbar: 图片底部工具栏的占位
预览效果/Preview

图片列表

图片预览

绘图

调整大小
补充说明
联系我/Contact me
网友评论