【合集】前端图片解决方案

2,657 阅读22分钟

前言

写作背景

最近做了很多业务都涉及到了图片相关的内容,因此 leader 建议我整理下业务上常用的图片相关内容,方便组内同学再遇到相同问题时有对应的解决思路,也由此得出了此文,希望也能给遇到相关问题的同学有所帮助。

阅读说明

本文是从用户操作习惯上对图片方案进行分类编写,从上传、下载最后到展示以及其他,分为四大类说明。虽说是合集,但是必然会有些场景有所遗漏,因此阅读前可以先看看下图是否有所需要的主题:

重点

  1. 本文只是整合方案,提供技术思路,因为有些在业务中作者并未实操过,所以这些内容无法确保可行性。(一般可行啦,但是可能会有坑。。)
  2. 本文不适合前端初学者,并不是从 0 到 1 的一步步告知方案,但是初学者可以自行搜寻相关资料得到更详细的介绍。

上传相关

图片选择

图片类型选择

input 的 accepte 属性,可以设置如下

上传文件 设置的accepte属性
png image/png
gif image/gif
jpeg或jpg image/jpeg
任意图片 image/*
... ...

1.1 多种类型用, 分割,如 accept = "image/png,image/jpeg"

1.2 此处只是限制了浏览器的选择文件属性,用户仍旧可以通过操作,改为选择全部文件,上传不符合预期的文件类型

图片多选

多选可以通过 input 元素的属性 multiple 控制

注意

无法通过原生控制选择图片的数量(如果有可以留言告知一下,谢谢~),如果需要做图片数量的控制,需要选择完图片后,在 input 的 onchange 中监听文件的数量进行判断。

建议

  1. 在选择上传的按钮附近加上明显的提示,告知用户最多上传的图片数量。至于上传超过数量时逻辑,可以自行根据业务场景决定:是上传上限内的前几张图片,或是全部都不上传;
  2. 在 APP 端嵌入H5页面,使用 input 原生上传方案时,在选择图片时是使用 APP 做的选择图片功能,即选择图片的上限可能会受 APP 端的限制。

粘贴选择图片

此需求暂时未在作者业务中应用,因为作者的常用上传场景是,一个页面中含有多个单个上传按钮,粘贴选择图片上传,适合单个页面中仅有单个选择器的场景(个人见解哈~)。

核心逻辑是监听paste粘贴事件,如下图,一个很简单的监听即可。注意是此处浏览器是有计算处理的,因此打印出来的值可能会有误差,如图2,展开对象后就无法看到值。

更详尽的介绍可以看看张鑫旭老师的博客:直接剪切板粘贴上传图片的前端JS实现

注意:

1 很多资料都说该API是无法监听本地文件下图片的内容,但是发现有些网站可以,不知道是怎么实现的,有了解的同学可以告知下。

2 需要关注该API不同浏览器的表现形式,即兼容性。

上传文件前校验

不管是用antd等UI库,亦或是使用原生的input,核心点基本都是基于 input 的 onchange 事件中进行逻辑编写,在此事件中可以拿到文件的一些相关信息,其结构大概如下:

通过拿到上图文件信息去做相应的比较

1 文件大小,上图的 size ,单位是字节,如果转成了 Base64 之后,通过一定计算,也可以得到文件的长度。根据 base64 的转换规则:3个8位字节(3*8=24)转化为4个6位的字节(4*6=24),之后在6位的前面补两个0。,得到示例代码如下:

computeFileSize: (base64) => {
// base64头部例如的data:image/jpg;base64 
let str = base64.split(',')[1]; 
const equalIndex = str.indexOf('='); 
if (str.indexOf('=') > 0) {
    //找到等号,把等号也去掉
    str = str.substring(0, equalIndex);
}
const length = str.length;
// 计算后得到的文件流大小,此时单位为字节
const fileSize = ~~(length - (length / 8) * 2); 

return fileSize / 1024; // 转换单位为 KB 

2 文件后缀名,上图的type,此处的type是你上传文件时的文件后缀名,而非该文件的真实后缀名,比如你将一个Excel文件后缀名改为.jpeg,此处的type是image/jpeg,而非Excel,如果想要校验真实的文件类型,可以参考我以前的文章:核心逻辑是读取文件的二进制流开头

上传文件前预处理

核心逻辑都是使用 Canvas 重绘一次图片,需要注意的是 Canvas 绘图时,要在 image onload 后进行操作。因为 Canvas.drawImage 方法接受的第一个参数是 canvasImageSource 。或许 createImageBitmap 这个 API 可以不用在 image.onload 中执行,因为它的文档里写着可以用 Blob 加载,可以使用 fileReader 读到文件二进制流后进行渲染。但是此方法未尝试过,有兴趣的同学可以试试。

图片裁剪

此块作者还未在实际项目中运用过,这里放上别人的方案:canvas裁剪图片

思路大概如下:得到裁剪框距待裁剪图片的相对位置的定点坐标,即图中红点,使用的API为 offsetTop 、 offsetLeft

W1 是被裁剪的图片宽度,W2是裁剪框的宽度,得到宽高、坐标就可以通过调用 canvas的 drawImage 进行裁剪绘图了。API摘自W3School,如下:

drawImage参数(已按顺序) 含义
img 规定要使用的图像、画布或视频。
sx 可选。开始剪切的 x 坐标位置。
sy 可选。开始剪切的 y 坐标位置。
swidth 可选。被剪切图像的宽度。
sheight 可选。被剪切图像的高度。
x 在画布上放置图像的 x 坐标位置。
y 在画布上放置图像的 y 坐标位置。
width 可选。要使用的图像的宽度。(伸展或缩小图像)
height 可选。要使用的图像的高度。(伸展或缩小图像)

举个例子:比如现在原图的宽高分别为 534 * 345 ,裁剪框的宽高为 434 * 145,红点坐标位置为 (50,100) 。那么 drawImage(img,50, 100,434, 145,0, 0,534, 345),得到的效果就是裁剪的图,且放大到跟原图一样大小。

注意:必须所有7个参数都传入,因为这个的接口定义必须3 or 5 or 8个参数都传,如下图:

自动摆正

利用 EXIF.js 库完成,这类的相关文章也有很多了,可以自行搜索,也可以看我之前的文章,这里也不再赘述了,文章地址上传旋转

注意:现在的浏览器可能会自动将其进行摆正:浏览器旋转兼容性,解决方案如下,方法来源:JavaScript-Load-Image

const testAutoOrientationImageURL =
    'data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAA' +
    'AAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA' +
    'QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE' +
    'BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAEAAgMBEQACEQEDEQH/x' +
    'ABKAAEAAAAAAAAAAAAAAAAAAAALEAEAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAA' +
    'AAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8H//2Q==';

let isImageAutomaticRotation; // 判断浏览器是否默认支持旋转

detectImageAutomaticRotation: () => (
    // 用一张特殊的图片来检测当前浏览器是否对带 EXIF 信息的图片进行回正
    // 方法来源: https://github.com/blueimp/JavaScript-Load-Image/blob/6dc04e85d62d395d93c4bfdd35644772027671d1/js/load-image-orientation.js
    new Promise((resolve) => {
        if (isImageAutomaticRotation === undefined) {
            const img = new Image();
            img.src = testAutoOrientationImageURL;
            img.onload = () => {
                        // 如果图片变成 1x2,说明浏览器对图片进行了回正
                isImageAutomaticRotation = img.width === 1 && img.height === 2;
                resolve(isImageAutomaticRotation);
            };
        } else {
            resolve(isImageAutomaticRotation);
        }
    })
)

const checkIsAutoRotate = await detectImageAutomaticRotation();
if (true === checkIsAutoRotate) {
    ctx.drawImage(this, 0, 0, imgWidth, imgHeight);
} else {
    // 利用EXIF 判断图片之前的旋转角度
}

添加水印

水印因为是看业务需求而定,具体逻辑就不放出来了,各位看官可以根据业务实际绘制效果去搜索一下相关点好,这类文章已经很多了。

const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
// image实例对象
const imageUpload = new Image();
imageUpload.src = originImgUrl;
imageUpload.setAttribute('crossOrigin', 'anonymous');
imageUpload.onload = () => {
    canvas.width = imageUpload.width;
    canvas.height = imageUpload.height;
    // canvas绘制图片
    context.drawImage(imageUpload, 0, 0);
    // 绘制水印逻辑,比如右下角加个名字等
    ...
    // 绘制完成后导出图片URL,此处格式为 base64 
    canvas.toDataURL('image/jpeg')
}

注意:请先调研清楚公司平台的文件存储服务会不会自动加水印,不然届时叠了两层水印,跟叠杀人书似的。

图片压缩

图片压缩目前常用的方案是对图片的长、宽按比例压缩,一般业务已经足以达到预期。此类文章也很多了,可以自行搜索引擎,或者我以前文章也有提过:上传压缩

如果不想通过调整图片尺寸,可以尝试修改图片的位深等属性,此块未做过调研,不一定可行。

正式上传

单张上传

作者部门业务上主要传输的分为base64、文件流两种 如果传输的是文件流类型,一般通过 multipart/form-data 这种请求头形式上传,文件流作为主参数传递。

如果传输的是Base64,得关注下,因为在 form-data 下,Base64 中有 + 号,在传输时,+ 号是会被处理成空格,base64空格问题。前端对 base64 进行 encodeURIComponent,后端进行相应的解码即可。

注意:此处结论不一定准确,因为仅是从 JSON 传输、文件流两种传输的表现结果上得出的,不清楚是否有其他请求头或别的原因导致。

多张

多张情况下,目前使用的Base64传递

将多张图片的 base64 放入一个数组中,再进行JSON传输。(base64问题同上)

断点续传

还未实际应用在业务中,真正要用到业务中的同学注意坑。

  1. 文件流通过slice切片,并转成类似对象的格式,提交给后端
{
    file: 切片的文件,
    index: 切片的下标,
    size: 此切片的大小,
    totalSize: 总文件大小,
    totolCount: 一共切分了几片,
}
  1. 前端监听到传输的切片下标是最后一个文件片段时,告诉后端此文件已经传输成功。(可以是通过另外一个接口)
  2. 亦或者是,原有的文件长度或者是总切片已知,后端拿到文件片段时,可以知道当前的文件片段是否是最后一个文件(index === totolCount),或者是 先前的所有文件 size 加上本文件 size 等于 totolSize 也能让后端知道文件已传输完成,后端进行 merge 文件操作。

实际代码可以尝试下此文:断点续传

因为作者一般大文件的图片需求较少;二是大文件的图片对其做渲染的时候,有一定的优化成本。所以很少在业务场景中真正需要对图片做断点续传操作。做云服务的话除外哈~

下载

下载方案,可以前端实现、也可以做成后端实现,可以视双方的技术成本加以评估再做决定。其实这块不仅图片可适用,其他文件也可。

单张图片下载

  1. 右键直接另存为图片(PC端),长按图片弹出类似右键菜单的操作选项,保存图片(移动端)
  2. 使用原生 a 标签下载
  3. JS控制下载,先通过 AJAX 等请求方式,从服务端领取到 文件源(二进制流对象) 或者图片地址等,经过自身业务逻辑(如校验等),然后再传入到一个 a 标签中,同第二步原理

多张图片下载(打包下载)

  1. 需告知后端选中的图片标识信息(如图片的地址,文件流等等,视各方业务而定),后端返回多张图片打包好的压缩包文件流,即可走单张图片下载的第三步逻辑。此方案优点是体验尚可,前端成本也较低,缺点是需要后端配合;
  2. 拿到所选文件数组,通过调用库:JSZip,将图片合入Zip压缩文件中再下载。此方案优点是体验尚可,无需后端配后,缺点是需要引入第三方库,体积、前端成本略大;
  3. 拿到所选文件数组,通过遍历数组,不断构建 a 标签元素,触发单张图片下载。此方案优点是无需后端配后,前端成本尚可,缺点是体验上是略有不足,此方案更应该称为批量下载:点一下按钮,浏览器下载10个图片。

展示相关

水印

水印逻辑上方已提过,不再赘述

旋转

调用 CSS3 的 Rotate 基本可以满足日常业务。

注意:Rotate 是 transform 下的一个属性值,因此有其他API使用时,注意是否会影响到其他属性。

比如:一开始是:transform:rotate(7deg) translate(40%,20%);

后经过动画后,想旋转90度,但是代码写成了transform:rotate(97deg) (忘了同步加上translate,造成位置丢失),应该写成 transform:rotate(97deg) translate(40%,20%);

缩放

主要是应用 transorm 的属性 scale ,zoom 属性也可以完成缩放功能,但是 zoom 不属于规范定义的属性。

  1. zoom的缩放是相对于左上角的,而scale默认是居中缩放;
  2. zoom的缩放改变了元素占据的空间大小,而scale的缩放占据的原始尺寸不变,页面布局不会发生变化。(即zoom会触发重排,scale不会)
  3. 对文字的缩放规则不一致。zoom缩放依然受限于最小 12px 中文大小限制;而 scale 就是纯粹的对图形进行比例控制,因此文字支持小于 12px。

推荐还是使用 scale,但是注意他也是 transform 中的一个属性问题(即同旋转)。

轮播 / 全屏

可以将图片轮播理解为横向滚动,全屏滚动理解为纵向滚动。

图片轮播的原理分为两种:(以 ABCD 4张图片为例)

第一种方案

是将垂直方向上的轮播,可以理解为类比 PS 图层重叠,如下:

A 展示

B 隐藏

C 隐藏

D 隐藏

利用JS 控制图片的显隐,CSS3控制“障眼法”,主要是translate(移位)+ animation (动画)。

这种可以适用于轮播每次只展示完整一张图片的交互设计。

第二种方案

如同视觉上的轮播,将图片横向排列 如 [ A B ] C D(方框内的,表示现在正在展示的)

  1. 给整个轮播外层Div固定一个宽度,超出隐藏
  2. 每次切换图片都是最左的图片左偏移(或者是最右的图片右偏移),然后加上CSS3 动画特效。
  3. 当来到最后一张图片时(即D),在D后面实际上有一张 A 的备份图,用于障眼法。
  4. 正常的动画继续从D到A,在到A(备份)的时候,把图片的所有的左偏移值置为0,即相当于此时回到了真正的第一张图片,且用户无感知(此时的切换不用加动画特效)。
  5. 然后代码会继续从第一张切到第二张等,达到无缝切换的效果。
  6. 从第一张 A 到 最后一张 D 时 同理
  7. 综上,采用这种的轮播实际上的图片列表是 D A B C D A

对比上一种,这种的方法实现上难度稍大,但是可以支持多张图片同时出现在视区的这种交互设计。

全屏滚动 这种交互主要是:每屏都都仅展示一个高度贴合的内容,然后一般是 右侧 或者 左侧 留有导航栏,每滚动一屏幕,导航栏对应的那一项即高亮,如图:

主要是利用原生的 onscroll 事件与 Element.scrollIntoView 两种 API 处理。

因为是全屏监听,因此高度是已知的,即屏幕高度。

监听 onscroll 滚动高度 / 屏幕高度,即可得之现在处于第几屏。然后高亮标识导航栏的对应目录。scrollIntoView 逻辑类似,不再赘述。

建议使用 scrollIntoView ,因为性能上更优。

全屏与轮播两种需求,都不建议自己编写,建议使用第三方库:

轮播 推荐swiper(最高star)

全屏 有对应的react、Vue、Angular版本,可自行寻找

懒加载

最简单的方法,但是仅有chrome 支持:img 标签上loading属性写lazy 自动懒加载,详情可以参考:深入理解图片和框架的原生懒加载功能

常用实现原理:

先给 img 的 src 属性赋予的是默认的一张占位图,这种图片尽量体积小,大小设为与真正展示的图片一致,还能起到保持页面布局的作用(有点骨架屏那意思)

通过 html 元素的 data-* 自定义属性为每个图片附上真正要展示的图片地址,伪代码如下:

<img src=”一张默认图” data-src=”真正展示的图片” />

当该 img 元素快要到浏览器视区中时,就用data-* 中真正的连接地址赋予到图片的src上。因此主要的核心逻辑:还是监听滚动区与元素的位置计算,即scrollTop与offsetTop。核心判断逻辑如下:

clientHeight = document.documentElement.clientHeight; // 视区高度

clientHeight + scrollTop > img.offsetTop // 如果为 true 表示元素已经进入到可视区

可以视情况加上部分的缓冲高度,提前请求图片,提前加载,交互上更友好。

基于上述方案,可以有以下优化手段:

1 加上节流函数(throttle)

2 IntersectionObserver 个人认为更为优雅的监听方案(但是不支持ie)

3 虚拟长列表 ,即只利用少量的视区中的dom节点 去模拟一个长的list 节点。(www.npmjs.com/package/rea…)

懒加载不建议自己编写,常用的库有:

lazyload

react-lazyload

预加载

预加载除了会让交互体验更为友好外,还有一个用途是处理图片的闪动:加载背景图初次显示会闪一下的情况,可以使用预加载方法解决。

主要原理是利用浏览器的缓存机制(链接一样,浏览器就会调用缓存),有2种常用方案:

  1. 最简单,提前在CSS中 用 backage-image 加载图片,但是不要展示(比如隐藏、放到视区屏幕外等)

注意:Opera 和 Firefox 对 display:none 的元素的背景,不会立即发生请求,只有当其 display != none 才会发起图片请求,其他浏览器则是立即发起请求。

  1. 直接使用 new Image 加载,至于时机,可以视业务决定:
const images = []
function preload(list) { 
	for (i = 0; i < list.length; i++) { 
		images [i] = new Image() 
		images [i].src = list [i] 
	} 
} 
preload( 
	["图片1", "图片2", "图片3" ,”图片4”]
); //调用预加载图片函数,调用时机业务决定。

自适应图片展示

自适应现在已经有很多方案(多的甚至可以知乎开专栏了),这些通用方案也能用于图片元素。因此这里只提图片元素特有的。

1 srcset 属性(IE不兼容),srcset属性用于设置不同屏幕密度下,image自动加载不同的图片,如图:

可以让设计出好不同屏幕下适配的图,加上一点点的样式控制,就能实现比较通用的图片自适应。方案来源

2 object-fit & object-position 属性

一张原图:

含义 示例
fill 图片拉伸填满整个容器,不保证保持原有的比例。
contain 在保持图片比例情况下,保证图片一定可以在容器里面放得下。此参数可能会让图片的容器内留有空白。
cover 在保持图片比例情况下,保证图片尺寸一定大于容器尺寸,且宽度和高度至少有一个和容器一致。此参数可能会让图片的部分区域不可见。
none 在保持原有尺寸比例下,同时保持图片原始尺寸大小。
scale-down none + contain的效果, 最终呈现的是尺寸比较小的那个。即容器大于图片时,图片的效果如 none,如果是容器小于图片,图片效果如contain。

object-position 控制图片展示位置,默认值是50% 50%,也就是居中效果,如上图的 object-fit ,图片都是水平垂直居中的。

object-fit + object-position 能解决挺多的图片适应性问题。

查看大图

一般场景如下:默认展示的是缩略图,提供一个入口可以点击缩略图,弹出弹窗查看该图片的放大版本,且支持旋转、缩放等等操作。

原理可以结合上述提到的CSS3 rotate、scale 等属性,加上模态框弹窗处理。一般情况下,不建议再造这种轮子,现在社区上的这种UI库已经挺多,比如:

Viewer.js Zooming

其他

防盗链

同登录校验等逻辑,实际上都是服务端校验请求头上的参数,决定是否允许返回该图片资源。在图片上,最常用的是 referer 头。常用思路是:

  1. 当 referer 为空时, 返回正确的图
  2. 当 referer 不为空, 且 host 命中白名单网站时, 返回正确的图
  3. 当 referer 不为空, 但 host 未能命中白名单网站时, 返回错误的图

其他方案是,给资源增加时间限制,一般这种图片的 URI 都会带有唯一的标识,如:

https://images.cdn.com/image/608782/201503/291535318955336.jpg(后面的数字就是一组唯一的标识)

提供一种方案逻辑大致如下,注意:图上的 CDN 是指带一定的逻辑处理的服务器,并不是只管静态存储那种。

webpack 图片转 Base64 嵌入文件

Webpack 可以通过 url-loader 来将图片转成Base64 ,但是不建议所有图片都转成Base64,虽然转了后,可以减少一次请求,但是会增加CSS的体积,带来CSP问题,可以参考这篇文章:【前端攻略】:玩转图片Base64编码

module.exports = {
    module: {
        rules: [
           {
                test: /\.(gif|png|jpg|woff|svg|ttf|eot)$/ ,
                use:[{
                    loader:'url-loader',
                    options: {
                        limit:500,
                        outputPath: 'images/',
                        name:'[name].[ext]'
                    }
                }]
            }
        ]
    }
};

通过url-loader的 limit 配置项来完成,单位byte,小于limit的图片资源会进行 base64 编码转换,目前作者业务上用的脚手架上设为 204800 ,即200KB。

雪碧图

目前在 web 端基本不建议再使用(但没有完全淘汰,图像领域、游戏领域还在使用),原因如下:

  1. 网页中目前用小图片素材已经比较少了,iconfont 的出现,大大减少了雪碧图的使用场景。
  2. 雪碧图的出现是为了解决很多小图片带来的很多额外的请求,不得已而为之的办法,因为其本身给设计和开发都带来了额外的成本,那些小图标位置确定了之后就不能随便改了,大小也不能随便改,不能动到其它图标的位置,有较多局限。
  3. HTTP2的多路并发,很大程度上解决了额外请求带来的创建,请求通信的消耗

图片 base64 转文件流

此类方案网上有很多,这里放上的是 Twitter 的做法:www.zhihu.com/question/30…

function convertCanvasToBlob(canvas) {
    var format = "image/jpeg";
    var base64 = canvas.toDataURL(format);
    var code = window.atob(base64.split(",")[1]);
    var aBuffer = new window.ArrayBuffer(code.length);
    var uBuffer = new window.Uint8Array(aBuffer);
    for(var i = 0; i < code.length; i++){
        uBuffer[i] = code.charCodeAt(i);
    }
    var Builder = window.WebKitBlobBuilder || window.MozBlobBuilder;
    if(Builder){
        var builder = new Builder;
        builder.append(buffer);
        return builder.getBlob(format);
    } else {
        return new window.Blob([ buffer ], {type: format});
    }
}

img埋点

目前利用空白的 GIF 或1x1 px的 GIF 是互联网广告或网站监测方面常用的手段,简单、安全,原因如下:

  1. 避免跨域( img 元素原生支持跨域);
  2. 图片请求不占用 Ajax 请求限额;
  3. GIF 的最低合法体积最小(最小的 BMP 文件需要74个字节,PNG需要67个字节,而合法的 GIF,只需要43个字节)
  4. 相比 PNG/JPG 体积小,1px 透明图,对网页内容的影响几乎没有影响;
  5. 不会阻塞页面加载,影响用户的体验,只要 new Image 对象(异步),一般情况下不需要 append 到 DOM 中(存在内存中),通过它的 onerror 和 onload 事件来检测发送状态。

注意:无图模式问题下,图片不会发出请求的,即不会 onload、onerror 事件都不会触发,因此不推荐使用这种方式去发页面请求逻辑。

Canvas 绘图

使用 Canvas 绘图遇到一片黑或者一片白的问题,有三种可能

  1. 图片跨域
  2. 没在image.onload 函数中再调用绘图函数
  3. 没有调用 darwImage 等API 真正绘图

跨域问题:

Canvas 跨域问题其实有两种不同的情况:

第一种:可以拿到图片

可以正常的通过 Canvas 渲染,但是对其进行操作,如 toDataURL、getImageData 等操作时会提示跨域,对于这种情况,解决方案是:

可以通过Ajax请求,去获取到本图片,然后转文件流后再使用 Canvas 进行操作。

第二种:图片无法访问

通过调用 new Image、AJax 都无法访问,此时是图片还未下载就已经提示跨域,即还未进入到Canvas任何操作,对于这种情况,暂时没有纯前端技术上的解决方案。

目前的方案有三种:

  1. 是用户主动关闭浏览器的跨域拦截;
  2. 需要服务端将该图片转成非跨域的资源:如 base64、文件流、上传到CDN等;
  3. 是交互上提示用户先将该图片下载,然后通过将该文件上传,Canvas 再处理本地文件流,从而解决问题。

结尾

因为本文并非所有方案都亲身用于在项目中,难免会理解不到位,如有错误,还请帮忙指出~ Thanks♪(・ω・)ノ😊。

最后希望此文能帮上在前端图片上有困难的同学~