前言
因为老大突然说 网站后台负责维护的人员 上传到富文本(为了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的提问。
有需要的小伙伴,可以点击下方卡片领取,无偿分享
- 字符| 表达式 | 描述 || — | — ||