文章目录
- 先实现正常的效果
- 实现下载图片
- 改变viewerjs的build函数
- 源码改变之后,执行npm i 之后node_modules源码又变回了原样
1、viwerjs所有功能都很完善,但唯独缺少了图片的下载
2、需求:在用viwerjs旋转图片后,可以直接下载旋转后的图片
效果:
先实现正常的效果
1、安装v-viewer(一个对viwerjs的使用方式优化的npm包)
npm i v-viewer
2、
main.js文件 (vue2版本)
import Vue from 'vue'
import App from './App.vue'
//哪怕使用v-viewer也需要导入正常的viwerjs的css文件
import 'viewerjs/dist/viewer.css'
import Viewer from 'v-viewer'
Vue.use(Viewer, {
defaultOptions: {
//里面可以填一些配置项,这里我先不填
}
})
new Vue({
render: h => h(App),
}).$mount('#app')
app.vue文件中使用组件
<template>
<div class="main">
<viewer ref="viewer">
<img
src="https://img1.baidu.com/it/u=413643897,2296924942&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500"
alt=""
data-uid="uid字段"
ref="img"
/>
</viewer>
</div>
</template>
<script>
export default {
data() {
return {};
},
props: {},
watch: {},
mounted() {
},
created() {},
methods: {},
};
</script>
<style scoped>
</style>
效果
实现下载图片
在main.js中添加配置项
可以先看下面build函数的改动,再看main中的download
import Vue from 'vue'
import App from './App.vue'
import 'viewerjs/dist/viewer.css'
import Viewer from 'v-viewer'
Vue.config.productionTip = false
//可以先忽略base64toBlob函数,就是个base64文件转blob的函数
function base64toBlob(dataurl) {
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
});
}
Vue.use(Viewer, {
defaultOptions: {
//下载事件的回调
download(viewer) {
//当前显示图片的索引
const index = viewer.index
//所有图片构成的数组
const list = viewer.images
//当前显示的图片
const ima = list[index]
const canvas = document.createElement('canvas') //获取canvas
//对应的CanvasRenderingContext2D对象(画笔)
let img = new Image() //创建新的图片对象
let base64 = ''; //base64
img.src = ima.src + '?' + new Date().getTime();
//如果您仅仅是下载当前图片, img.src便是当前的图片地址
//下面的内容就完全可以不用看了
//自行百度:前端拿到图片地址,如何下载图片
const rotate = viewer.imageData.rotate
let x, y
const type = rotate / 90 % 4
img.setAttribute("crossOrigin", 'Anonymous')
img.onload = function () { //图片加载完,再draw 和 toDataURL
let width = img.width
let height = img.height
const flg = width == height
//这里写的不是很好(4个if判断),有更好的书写方式可以告知我一下,除了(switch、改成object的形式)
//0、1、2、3对应旋转0°、90°、180°、270°
//0的时候不需要特处理
if (type == 0) {
x = 0;
y = 0
} else if (type == 1) {
//1的时候需要考虑一种清空,如果他是个矩形图片,也就是宽高不相等
//在旋转90°后,我们需要调整canvas的宽高、同理270°一样
x = height;
y = 0;
if (!flg) {
const blg = width
width = height
height = blg
}
} else if (type == 2) {
x = width;
y = height;
} else if (type == 3) {
x = 0;
y = height;
if (!flg) {
const blg = width
width = height
height = blg
}
}
canvas.width = width
canvas.height = height
let ctx = canvas.getContext("2d")
ctx.translate(x, y);
ctx.rotate(rotate * Math.PI / 180);
ctx.drawImage(img, 0, 0, img.width, img.height);
//图片转base64
base64 = canvas.toDataURL("image/png");
//base64转链接,该链接便是用户旋转后的图片了
//如果想下载该图片,直接定义a标签,然后触发点击事件下载即可,可自行百度拿到链接,如何下载链接的内容
console.log('url___', URL.createObjectURL(base64toBlob(base64)));
}
}
}
})
new Vue({
render: h => h(App),
router,
}).$mount('#app')
找到viwerjs源文件
/node_modules/viewerjs/dist/viewer.js
为什么不去v-viewer文件下找呢?上面说了,v-viewer是对viwwerjs的封装使用,似乎没有改动源码
在该文件下搜索
toolbar.appendChild(list)
该代码存在于build函数下面,作用是构建底部的工具按钮
改变viewerjs的build函数
build函数源代码(看我写的注释)
其中注释最多的地方便是对源码的改动
{
key: "build",
value: function build() {
if (this.ready) {
return;
}
//关键变量之一,当前viewerjs挂载的dom
var element = this.element,
//关键变量,当前的viewer的option配置项
options = this.options;
var parent = element.parentNode;
var template = document.createElement('div');
template.innerHTML = TEMPLATE;
var viewer = template.querySelector(".".concat(NAMESPACE, "-container"));
var title = viewer.querySelector(".".concat(NAMESPACE, "-title"));
//toolbar 关键变量之一,下面代码是获取toolbar的dom元素
var toolbar = viewer.querySelector(".".concat(NAMESPACE, "-toolbar"));
var navbar = viewer.querySelector(".".concat(NAMESPACE, "-navbar"));
var button = viewer.querySelector(".".concat(NAMESPACE, "-button"));
var canvas = viewer.querySelector(".".concat(NAMESPACE, "-canvas"));
this.parent = parent;
this.viewer = viewer;
this.title = title;
this.toolbar = toolbar;
this.navbar = navbar;
this.button = button;
this.canvas = canvas;
this.footer = viewer.querySelector(".".concat(NAMESPACE, "-footer"));
this.tooltipBox = viewer.querySelector(".".concat(NAMESPACE, "-tooltip"));
this.player = viewer.querySelector(".".concat(NAMESPACE, "-player"));
this.list = viewer.querySelector(".".concat(NAMESPACE, "-list"));
viewer.id = "".concat(NAMESPACE).concat(this.id);
title.id = "".concat(NAMESPACE, "Title").concat(this.id);
addClass(title, !options.title ? CLASS_HIDE : getResponsiveClass(Array.isArray(options.title) ? options.title[0] : options.title));
addClass(navbar, !options.navbar ? CLASS_HIDE : getResponsiveClass(options.navbar));
toggleClass(button, CLASS_HIDE, !options.button);
if (options.keyboard) {
button.setAttribute('tabindex', 0);
}
if (options.backdrop) {
addClass(viewer, "".concat(NAMESPACE, "-backdrop"));
if (!options.inline && options.backdrop !== 'static') {
setData(canvas, DATA_ACTION, 'hide');
}
}
if (isString(options.className) && options.className) {
// In case there are multiple class names
options.className.split(REGEXP_SPACES).forEach(function (className) {
addClass(viewer, className);
});
}
if (options.toolbar) {
var list = document.createElement('ul');
var custom = isPlainObject(options.toolbar);
var zoomButtons = BUTTONS.slice(0, 3);
var rotateButtons = BUTTONS.slice(7, 9);
var scaleButtons = BUTTONS.slice(9);
if (!custom) {
addClass(toolbar, getResponsiveClass(options.toolbar));
}
//关键函数之一,forEach,是对传统foreach的1封装
//BUTTONS关键变量之一,是一个数组,里面存放的是底部操作工具栏信息
//下面的foreach的解读:根据BUTTONS这个数组构建出底部工具栏按钮,并且给对应的按钮添加一些属性信息
forEach(custom ? options.toolbar : BUTTONS, function (value, index) {
var deep = custom && isPlainObject(value);
var name = custom ? hyphenate(index) : value;
var show = deep && !isUndefined(value.show) ? value.show : value;
if (!show || !options.zoomable && zoomButtons.indexOf(name) !== -1 || !options.rotatable && rotateButtons.indexOf(name) !== -1 || !options.scalable && scaleButtons.indexOf(name) !== -1) {
return;
}
var size = deep && !isUndefined(value.size) ? value.size : value;
var click = deep && !isUndefined(value.click) ? value.click : value;
var item = document.createElement('li');
if (options.keyboard) {
item.setAttribute('tabindex', 0);
}
item.setAttribute('role', 'button');
addClass(item, "".concat(NAMESPACE, "-").concat(name));
if (!isFunction(click)) {
setData(item, DATA_ACTION, name);
}
if (isNumber(show)) {
addClass(item, getResponsiveClass(show));
}
if (['small', 'large'].indexOf(size) !== -1) {
addClass(item, "".concat(NAMESPACE, "-").concat(size));
} else if (name === 'play') {
addClass(item, "".concat(NAMESPACE, "-large"));
}
if (isFunction(click)) {
addListener(item, EVENT_CLICK, click);
}
list.appendChild(item);
});
toolbar.appendChild(list);
} else {
addClass(toolbar, CLASS_HIDE);
}
//代码走到这一步,表示toolbar基本按钮已经配置完成了
//在这里来添加我们自己想加的按钮
//下面代码是我添加的功能
//开始
//isFunction:判断传递的download是不是对象,isFunction是viwerjs作者封装好的方法
//addListener:给某个dom元素添加事件监听,同样是viwerjs作者封装好的
//addListener(参数一是dom元素,参数二是事件名称,参数三是事件回调)
//addListener会有参数4,参数4是元素监听器的一些配置项,非必填
if(isFunction(options.download)){
//创建保存按钮
var download = document.createElement("li")
var that = this
//给按钮加事件
addListener(download,EVENT_CLICK,function(){
// options.download就是我上面main.js传递的download函数
//在点击该按钮后,同时执行download回调
//传递参数,that.element是viwerjs挂载的dom,
//that.element.viewer,viwerjs挂载后会为挂载的元素添加viewer属性(巨全的属性,里面啥都有),可以说我们拿到viewer基本所有的内容都可以拿到了
options.download(that.element.viewer)
})
//addclass是别人封装好的,给元素加类名,加完类名后直接改变css样式就行了,或者直接用行内style
addClass(download,'viewerjs-download')
list.appendChild(download)
}
///结束
if (!options.rotatable) {
var rotates = toolbar.querySelectorAll('li[class*="rotate"]');
addClass(rotates, CLASS_INVISIBLE);
forEach(rotates, function (rotate) {
toolbar.appendChild(rotate);
});
}
if (options.inline) {
addClass(button, CLASS_FULLSCREEN);
setStyle(viewer, {
zIndex: options.zIndexInline
});
if (window.getComputedStyle(parent).position === 'static') {
setStyle(parent, {
position: 'relative'
});
}
parent.insertBefore(viewer, element.nextSibling);
} else {
addClass(button, CLASS_CLOSE);
addClass(viewer, CLASS_FIXED);
addClass(viewer, CLASS_FADE);
addClass(viewer, CLASS_HIDE);
setStyle(viewer, {
zIndex: options.zIndex
});
var container = options.container;
if (isString(container)) {
container = element.ownerDocument.querySelector(container);
}
if (!container) {
container = this.body;
}
container.appendChild(viewer);
}
if (options.inline) {
this.render();
this.bind();
this.isShown = true;
}
this.ready = true;
if (isFunction(options.ready)) {
addListener(element, EVENT_READY, options.ready, {
once: true
});
}
if (dispatchEvent(element, EVENT_READY) === false) {
this.ready = false;
return;
}
if (this.ready && options.inline) {
this.view(this.index);
}
}
/**
* Get the no conflict viewer class.
* @returns {Viewer} The viewer class.
*/
}
源码改变之后,执行npm i 之后node_modules源码又变回了原样
利用patch-package解决该问题
安装
npm i patch-package
安装完成后,执行命令
npx patch-package viewerjs
//viewerjs是我们修改node_modules中的源码文件名 npx patch-package为固定内容
执行命令后发现,项目多了一个patches文件,这个便是对源码的补丁,我们需要把这个文件上传至项目仓库中,然后你同事拉下代码,执行npm i后会自动使用我们写的补丁