viewerjs 如何新增下载图片功能(npm包补丁)

文章目录

1、viwerjs所有功能都很完善,但唯独缺少了图片的下载
2、需求:在用viwerjs旋转图片后,可以直接下载旋转后的图片

效果:

先实现正常的效果

1、安装v-viewer(一个对viwerjs的使用方式优化的npm包)

bash 复制代码
npm i v-viewer

2、

main.js文件 (vue2版本)

javascript 复制代码
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文件中使用组件

html 复制代码
<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

javascript 复制代码
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的封装使用,似乎没有改动源码

在该文件下搜索

javascript 复制代码
toolbar.appendChild(list)

该代码存在于build函数下面,作用是构建底部的工具按钮

改变viewerjs的build函数

build函数源代码(看我写的注释)

其中注释最多的地方便是对源码的改动

javascript 复制代码
{
      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解决该问题

安装

bash 复制代码
npm i patch-package

安装完成后,执行命令

bash 复制代码
 npx patch-package viewerjs
 //viewerjs是我们修改node_modules中的源码文件名 npx patch-package为固定内容

执行命令后发现,项目多了一个patches文件,这个便是对源码的补丁,我们需要把这个文件上传至项目仓库中,然后你同事拉下代码,执行npm i后会自动使用我们写的补丁

相关推荐
天天进步2015几秒前
Node.js中的Prisma应用:现代数据库开发的最佳实践
数据库·node.js·数据库开发
YuJie1 分钟前
webSocket Manager
前端·javascript
Mapmost16 分钟前
Mapmost SDK for UE5 内核升级,三维场景渲染效果飙升!
前端
Mapmost19 分钟前
重磅升级丨Mapmost全面兼容3DTiles 1.1,3DGS量测精度跃升至亚米级!
前端·vue.js·three.js
wycode25 分钟前
Promise(一)极简版demo
前端·javascript
浮幻云月26 分钟前
一个自开自用的Ai提效VsCode插件
前端·javascript
DevSecOps选型指南27 分钟前
SBOM风险预警 | NPM前端框架 javaxscript 遭受投毒窃取浏览器cookie
前端·人工智能·前端框架·npm·软件供应链安全厂商·软件供应链安全工具
__lll_36 分钟前
Docker 从入门到实战:容器、镜像与 Compose 全攻略
前端·docker
木春1 小时前
react组件化思维:高复用性 UI 设计之道
前端·react.js