前言

 因为老大突然说 网站后台负责维护的人员 上传到富文本(为了SEO就不用贴图)的内容,一些图片显示失败,我一看还真是之前好不容易弄好这个富文本的图片上传功能(就是点击图片, 选择上传)还真没有想到他们直接扒过来别的网站内容(尤其里面包含图片)这时候加入这个网站设置跨域, 图片就会因为跨域显示403失败,无法加载出来. 

吐槽: 还以为他们富文本把文字写好, 在一个个上传图片, 组成一片文章. 那就没有办法了, 只能修改下代码增加下自动上传图片功能. 后来写到一半才发现html 有个属性好像可以让跨域的图片 显示出来.

 暂时不太理解这个代码, 也不清楚有没有副作用, 希望有懂的大佬说下. 

以为这样就可以不用写了, 但是老大说 万一以后别人网站的图片不维护了, 那这个引用还是导致图片显示失败, 还是上传到后台保险. 嘚, 代码还是要写.


1. 具体思路

​ 因为自己代码写得很烂, 就把关键的代码贴出来供大家参考, 当然不止WangEditor富文本编辑器能用, 其他地方需要粘贴时候自动上传图片也能实现, 原理都是一样的

​ (无非其他地方需要 自己选择DOM节点, 触发粘贴事件, 然后具体完成后, 在这个DOM节点插入 处理好的内容)

1.1 介绍过程

概念会如下再介绍, 先说说具体过程, 就是

  • 首先通过粘贴事件触发, 停止默认粘贴事件, 获取其text/html的内容
  • 使用字符串正则 match匹配 内容中符合

    1.1.2 image事件

    因为涉及到后面图片 转 base64

    image对象是JS中内置的对象, 当我们创建一个Image对象, 其实就是给浏览器缓存一张图片,

    在创建image对象后, width height默认0, 需要赋值, 同时还有src

    这里重点就是 onload事件

    当image的src发生改变,浏览器就会跑去加载这个src里的资源。这个操作是异步的.

    就是说,js不会傻傻地在原地等待图片的加载,而是继续读代码,直到图片加载完成,触发onload事件,js才会回来执行onload里面的内容。

    1.1.3 base64 & Blob & File

    因为上传到后台的请求时, 需要传入File类型, 而我们一开始只有url

    BASE64

    图片的 base64 编码就是可以将一副图片数据编码成一串字符串,使用该字符串代替图像地址。

    场景中,图片的下载始终都要向服务器发出请求,要是图片的下载不用向服务器发出请求,而可以随着 HTML 的下载同时下载到本地那就太好了,而 base64 正好能解决这个问题。

    一般如下

     

    Blob

    一个 Blob对象表示一个不可变的, 原始数据的类似文件对象。Blob表示的数据不一定是一个JavaScript原生格式 blob对象本质上是js中的一个对象,里面可以储存大量的二进制编码格式的数据。

    创建blob对象

    创建blob对象本质上和创建一个其他对象的方式是一样的,都是使用Blob() 的构造函数来进行创建。 构造函数接受两个参数:

    第一个参数为一个数据序列,可以是任意格式的值。

    第二个参数是一个包含两个属性的对象{ type: MIME的类型, endings: 决定第一个参数的数据格式,可以取值为 “transparent” 或者 “native”(transparent的话不变,是默认值,native 的话按操作系统转换) 。 }

    File

    一个FileList 对象通常来自于一个 HTML input 元素的 files 属性,你可以通过这个对象访问到用户所选择的文件,或者拖拽文件

    File 的构造函数很简单,使用 new File() 即返回一个新创建的文件对象

    1.1.4 字符串操作

    1.replace> replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。replace(" "," ") // 替换字符串中的字符(区分大小写)" "会自动转化成Regexp(正则表达式 / /)var a = "Visit Microsoft!";var b = a.replace("Microsoft","W3School");console.log(b); // Visit W3School! 2.match> match() 方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。

    var str="The rain in SPAIN stays mainly in the plain"; var n=str.match(/ain/g);// 结果ain,ain,ain 

    1.1.5 正则

    基本概念大致如下, 其他具体可以自己在查

    有一个网站能够检验自己的公式regex101.com/

    • 字符| 表达式 | 描述 || — | — || [abc] | 字符集。匹配集合中所含的任一字符。 || [^abc] | 否定字符集。匹配任何不在集合中的字符。 |* 分组和引用| 表达式 | 描述 || — | — || (expression) | 分组。匹配括号里的整个表达式。 |* 锚点和边界* 数量表示| 表达式 | 描述 || — | — || " />2.1 引入 & 富文本API

      需要引入的

      import md5 from "blueimp-md5"; // md5加密,后续会为了方便匹配(可以github搜这个blueimp-md5)import { ElLoading } from "element-plus"; // loading优化体验import { baseRequest } from "/@/api/invoke"; 

      首先因为用到了wangEditor, 会有一些API

      2.2 转换函数

      一些转换函数

      // 画布图片转base64function imageToBase64(img) {let canvas = document.createElement("canvas"); // 创建一个canvas对象// 初始化canvas.width = img.naturalWidth;canvas.height = img.naturalHeight;// 也是初始化, getContext("2d")这个方法表示创建一个2d的画布, 详情可以看文档let ctx = canvas.getContext("2d");// 把我们创建的图片传入, 画布创建ctx.drawImage(img, 0, 0, img.width, img.height);let ext = img.src.substring(img.src.lastIndexOf(".") + 1).toLowerCase(); // 拿后缀png这些// 我们要的base64就拿到了let dataURL = canvas.toDataURL("image/" + ext);return dataURL;} 
      // 创建image对象回调export const getImage = (url, callback) => {let image = new Image();image.setAttribute("crossOrigin", "*"); // 跨域image.src = url;//image.src = url + "" /> {let base64 = imageToBase64(image); // 这里就将我们的图片传入canvas了// 因为实在onload事件内, 所以结束要以回调的形式返回callback && typeof callback == "function" && callback(base64, url);};}; 

      src=“data:image/gif;base64,R0lGODlhHAAmAKIHAKqqqsvLy0hISOffffm5vf394uLiwAAAP///yH5B…EoqQqJKAIBaQOVKHAXrgBjboSvB8EpLoFZywOAo3LFE5lYs/QW9LT1TRk1V7S2xYJADs=”

      split通过 , 分割获取上面后面内容

      // base64转 Blobexport const dataURLtoBlob = dataurl => {var arr = dataurl.split(","),mime = arr[0].match(/:(.*?);/)[1], //获取前面的类型bstr = atob(arr[1]), // 获取后面内容n = bstr.length,u8arr = new Uint8Array(n); // 这里好像都是Uint8Array这个类型, 但不是太懂希望大佬告知while (n--) {u8arr[n] = bstr.charCodeAt(n);}return new Blob([u8arr], { type: mime });};//2,再将blob转换为fileexport const blobToFile = (theBlob, fileName) => {theBlob.lastModifiedDate = new Date(); // 文件最后的修改日期theBlob.name = fileName; // 文件名return new File([theBlob], fileName, {type: theBlob.type,lastModified: Date.now()});}; 

      2.3 请求上传函数

      请求上传函数, 这是我们那里的逻辑

      const urlArray = reactive(new Map()); //存储 urlfunction uploadImage(file, filename) {// 上传函数VITE是自己的后台地址let data = new FormData();data.append("file", file);const config = {method: "post",url: VITE... + "/admin/upload", // 上传图片地址 // 大致像这样 url: "http://192.168../.../admin/upload", //上传图片地址headers: { "Content-Type": "multipart/form-data", token: getToken() //这是我们的 需要token, 没有不写 }, data: data};axios(config).then(res => {const fileUrl = res.data.data.fileUrl; //获取后台设定传回的urlurlArray.set(filename, fileUrl); //添加到一个Map中});//return urlArray;} 

      2.4 自定义粘贴

      下面继续, 通过这个方法会触发自定义粘贴

      const customPaste = (editor, event, callback) => {openLoading();let html = event.clipboardData.getData("text/html"); // 获取粘贴的 html tempHtml.value = html; // 中间变量let srcArray = html.match(/

      3 错误和总结

      萌新, 方法和监听watch写的有点混乱, 想抽离出去, 但是很多内容要监听事件才能获取, 暂时没有弄得优雅,只想着实现就行

      3.1 如何替换

      ​ 另外就是 在写代码的 过程中, 遇到一个bug, 一直弄不好, 后来发现是由于没有彻底理解 image.onload()这个方法, 在很多图 同时for循环时

      • onlaod 表示触发图片加载完成, 但其实他们因为各种原因, 加载速度导致顺序不一定, 但是我以为遍历按顺序(例如复制的html包含图片A,B,C, D但是其实onlaod加载顺序可能是D, B, C, A) 如果把后台返回的url 放到一个数组里, 在依次取出数组里的url 去替换, 结果发现每次图片顺序不同导致 图片替换错误,(导致展示图片D, B C, A) 一直找了很久.> 如这个从浏览器缓存看到 每次顺序不一样, 所以特地用来Map, 需要一一匹配才能替换

      暂时想到只有这样, 大佬们如果有更好想法也可以告诉.

      3.2 如何验证图片全部上传完毕

      关于这个我是监听 Map的size 和 粘贴事件中 匹配图片数组的 长度相等时

      验证, 但不是很喜欢watch, 喜欢大佬有更好办法提出

      3.3 总结

      在依次取出数组里的url 去替换, 结果发现每次图片顺序不同导致 图片替换错误,(导致展示图片D, B C, A) 一直找了很久.> 如这个从浏览器缓存看到 每次顺序不一样, 所以特地用来Map, 需要一一匹配才能替换

      暂时想到只有这样, 大佬们如果有更好想法也可以告诉.

      [外链图片转存中…(img-AIXykEdz-1678088945405)]

      3.2 如何验证图片全部上传完毕

      关于这个我是监听 Map的size 和 粘贴事件中 匹配图片数组的 长度相等时

      验证, 但不是很喜欢watch, 喜欢大佬有更好办法提出

      3.3 总结

      这个自动上传功能说句实话很难, 主要一步步了解这些概念, 理解后才能继续写下去,总之花了很多时间, 另外第一次掘金写文章, 很多排版和设计不太美观, 希望大家给出建议, 或者哪里有学习参考文章 也可以推荐给我!

      最后

      整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。



      有需要的小伙伴,可以点击下方卡片领取,无偿分享