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

文章目录

    • 先实现正常的效果
    • 实现下载图片
    • 改变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后会自动使用我们写的补丁
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/68805.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【类和对象】收尾总结

目录 一、初始化列表 1.格式要求 (1) 初始化列表初始化 ①括号中是初始值 ②括号中是表达式 (2) 初始化列表和函数体混用 2.特点 ①初始化时先走初始化列表&#xff0c;再走函数体 ②拷贝构造函数属于特殊的构造函数&#xff0c;函数内也可以使用初始化列表进行初始化 …

【c语言】指针进阶(超详细)

文章目录 ✈ 指向函数指针数组的指针&#x1f4cc;指向函数指针数组的指针的定义&#x1f4cc;指向函数指针数组的数组指针的使用 ✈回调函数&#x1f4cc; 回调函数的定义&#x1f4cc; 回调函数的使用 ✈qsort函数&#x1f4cc; qsort函数的作用&#x1f4cc;qsort函数的定义…

Rikka with Square Numbers 2023“钉耙编程”中国大学生算法设计超级联赛(8)hdu7370

Problem - 7370 题目大意&#xff1a;给出两个数a&#xff0c;b&#xff0c;每次操作可以使其中一个数加上或减去一个任意的完全平方数&#xff0c;问要使a&#xff0c;b相等需要的最少操作次数是多少 1<a,b<1e9,a!b 思路&#xff1a;我们可以将问题转化为将a和b的差w…

最强的表格组件—AG Grid使用以及License Key Crack

PS: 想要官方 License Key翻到最后面 Ag Grid简介 Ag-Grid 是一个高级数据网格&#xff0c;适用于JavaScript/TypeScript应用程序&#xff0c;可以使用React、Angular和Vue等流行框架进行集成。它是一种功能强大、灵活且具有高度可定制性的表格解决方案&#xff0c;提供了丰富…

UNIX基础知识:UNIX体系结构、登录、文件和目录、输入和输出、程序和进程、出错处理、用户标识、信号、时间值、系统调用和库函数

引言&#xff1a; 所有的操作系统都为运行在其上的程序提供服务&#xff0c;比如&#xff1a;执行新程序、打开文件、读写文件、分配存储区、获得系统当前时间等等 1. UNIX体系结构 从严格意义上来说&#xff0c;操作系统可被定义为一种软件&#xff0c;它控制计算机硬件资源&…

博客项目(Spring Boot)

1.需求分析 注册功能&#xff08;添加用户操纵&#xff09;登录功能&#xff08;查询操作)我的文章列表页&#xff08;查询我的文章|文章修改|文章详情|文章删除&#xff09;博客编辑页&#xff08;添加文章操作&#xff09;所有人博客列表&#xff08;带分页功能&#xff09;…

Games101学习笔记2

参考博客&#xff1a;GAMES101 梳理 / 个人向图形学笔记_games101笔记_river_of_sebajun的博客-CSDN博客 lecture 05 Rasterization 1(Triangles) 光栅化 把东西画在屏幕上的过程就是光栅化的过程 视口变换 为什么模型用三角形&#xff1f; 最基本的几何平面&#xff1b;保…

matplotlib fig.legend()常用参数 包括位置调整和字体设置等

一、四种方法 legend() legend(handles, labels) legend(handleshandles) legend(labels)1 legend() labels自动通过绘图获取&#xff08;Automatic detection of elements to be shown in the legend&#xff09; # 第一种方法 ax.plot([1, 2, 3], labelInline label) ax.l…

JVM、JRE、JDK三者之间的关系

JVM、JRE和JDK是与Java开发和运行相关的三个重要概念。 再了解三者之前让我们先来了解下java源文件的执行顺序&#xff1a; 使用编辑器或IDE(集成开发环境)编写Java源文件.即demo.java程序必须编译为字节码文件&#xff0c;javac(Java编译器)编译源文件为demo.class文件.类文…

力扣:59. 螺旋矩阵 II(Python3)

题目&#xff1a; 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全…

日期切换

组件&#xff1a;<template><div class"time-picker"><el-radio-group size"small" v-model"timeType" change"changePickerType"><el-radio-button label"hour" v-if"isShow">时</el…

Open_PN笔记

>>>仅用作学习用途 1.准备好需要用到的工具 官网下载地址&#xff1a; openvpn 客户端下载地址&#xff1a; https://swupdate.openvpn.org/community/releases/openvpn-install-2.4.5-I601.exe EasyRSA下载地址&#xff1a; https://githu…

Koan自动重装和Cobbler_web

Koan是Cobbler的辅助工具&#xff0c;可以实现很多功能&#xff0c;使用koan配合Cobbler可以实现快速重装Linux系统&#xff1a; 1、安装koan&#xff1a; yum install -y epel-releaseyum install -y koan 安装截图&#xff1a; 2、在客户机上&#xff0c;用koan选择要重装的…

可以重复烧写的语音ic有哪些特征和优势

目录 一、简介可擦写的语音芯片&#xff0c;其实就是MCUflash的架构&#xff0c;无其他说法&#xff0c;就这一种说法。这个就是它最大的特征尤其是SOP8的封装类型的芯片&#xff0c;是区别于OTP类型的另一个品类&#xff0c;基本上OTP的语音芯片适用的场景。他都可以满足和替代…

文件系统目录结构

1. 目录结构 linux的文件系统是采用级层式的树状目录结构&#xff0c;在此结构中的最上层是根目录/ &#xff0c;然后在此目录下再创建其他的目录。 在linux中&#xff0c;一切皆文件(Linux将所有的设备、文件、进程等都当做文件来处理) 2. 目录作用具体介绍 目录名解析/b…

js玩儿爬虫

前言 提到爬虫可能大多都会想到python&#xff0c;其实爬虫的实现并不限制任何语言。 下面我们就使用js来实现&#xff0c;后端为express&#xff0c;前端为vue3。 实现功能 话不多说&#xff0c;先看结果&#xff1a; 这是项目链接&#xff1a;https://gitee.com/xi1213/w…

【云原生】kubernetes中容器的资源限制

目录 1 metrics-server 2 指定内存请求和限制 3 指定 CPU 请求和限制 资源限制 在k8s中对于容器资源限制主要分为以下两类: 内存资源限制: 内存请求&#xff08;request&#xff09;和内存限制&#xff08;limit&#xff09;分配给一个容器。 我们保障容器拥有它请求数量的…

【Spring专题】Spring之Bean的生命周期源码解析——阶段一(扫描生成BeanDefinition)

目录 前言阅读指引阅读建议 课程内容一、生成BeanDefinition1.1 简单回顾1.2 概念回顾1.3 核心方法讲解 二、方法讲解2.1 ClassPathBeanDefinitionScanner#scan2.2 ClassPathBeanDefinitionScanner#doScan2.3 ClassPathScanningCandidateComponentProvider#findCandidateCompon…

SpringBoot 整合JDBC

SpringData简介 Sping Data 官网&#xff1a;https://spring.io/projects/spring-data数据库相关的启动器 &#xff1a;可以参考官方文档&#xff1a;https://docs.spring.io/spring-boot/docs/2.6.5/reference/htmlsingle/#using-boot-starter 整合JDBC 创建测试项目测试数据…

访企聚力促创新:长安大学来访闪马智能

7月31日&#xff0c;长安大学运输工程学院院长葛颖恩教授、学院副书记李婷以及学办主任董彬一行来访闪马智能&#xff0c;闪马智能创始人兼CEO彭垚、城市交通行业总经理兼营销副总裁詹诚以及公共交通行业总经理熊天圣等出席了本次交流会。 长安大学运输工程学院院长葛颖恩教授…