前言
写作背景
最近做了很多业务都涉及到了图片相关的内容,因此 leader 建议我整理下业务上常用的图片相关内容,方便组内同学再遇到相同问题时有对应的解决思路,也由此得出了此文,希望也能给遇到相关问题的同学有所帮助。
阅读说明
本文是从用户操作习惯上对图片方案进行分类编写,从上传、下载最后到展示以及其他,分为四大类说明。虽说是合集,但是必然会有些场景有所遗漏,因此阅读前可以先看看下图是否有所需要的主题:

重点:
- 本文只是整合方案,提供技术思路,因为有些在业务中作者并未实操过,所以这些内容无法确保可行性。(一般可行啦,但是可能会有坑。。)
- 本文不适合前端初学者,并不是从 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 中监听文件的数量进行判断。
建议:
- 在选择上传的按钮附近加上明显的提示,告知用户最多上传的图片数量。至于上传超过数量时逻辑,可以自行根据业务场景决定:是上传上限内的前几张图片,或是全部都不上传;
- 在 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问题同上)
断点续传
还未实际应用在业务中,真正要用到业务中的同学注意坑。
- 文件流通过slice切片,并转成类似对象的格式,提交给后端
{
file: 切片的文件,
index: 切片的下标,
size: 此切片的大小,
totalSize: 总文件大小,
totolCount: 一共切分了几片,
}
- 前端监听到传输的切片下标是最后一个文件片段时,告诉后端此文件已经传输成功。(可以是通过另外一个接口)
- 亦或者是,原有的文件长度或者是总切片已知,后端拿到文件片段时,可以知道当前的文件片段是否是最后一个文件(index === totolCount),或者是 先前的所有文件 size 加上本文件 size 等于 totolSize 也能让后端知道文件已传输完成,后端进行 merge 文件操作。
实际代码可以尝试下此文:断点续传
因为作者一般大文件的图片需求较少;二是大文件的图片对其做渲染的时候,有一定的优化成本。所以很少在业务场景中真正需要对图片做断点续传操作。做云服务的话除外哈~
下载
下载方案,可以前端实现、也可以做成后端实现,可以视双方的技术成本加以评估再做决定。其实这块不仅图片可适用,其他文件也可。
单张图片下载
- 右键直接另存为图片(PC端),长按图片弹出类似右键菜单的操作选项,保存图片(移动端)
- 使用原生 a 标签下载
- JS控制下载,先通过 AJAX 等请求方式,从服务端领取到 文件源(二进制流对象) 或者图片地址等,经过自身业务逻辑(如校验等),然后再传入到一个 a 标签中,同第二步原理
多张图片下载(打包下载)
- 需告知后端选中的图片标识信息(如图片的地址,文件流等等,视各方业务而定),后端返回多张图片打包好的压缩包文件流,即可走单张图片下载的第三步逻辑。此方案优点是体验尚可,前端成本也较低,缺点是需要后端配合;
- 拿到所选文件数组,通过调用库:JSZip,将图片合入Zip压缩文件中再下载。此方案优点是体验尚可,无需后端配后,缺点是需要引入第三方库,体积、前端成本略大;
- 拿到所选文件数组,通过遍历数组,不断构建 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 不属于规范定义的属性。
- zoom的缩放是相对于左上角的,而scale默认是居中缩放;
- zoom的缩放改变了元素占据的空间大小,而scale的缩放占据的原始尺寸不变,页面布局不会发生变化。(即zoom会触发重排,scale不会)
- 对文字的缩放规则不一致。zoom缩放依然受限于最小 12px 中文大小限制;而 scale 就是纯粹的对图形进行比例控制,因此文字支持小于 12px。
推荐还是使用 scale,但是注意他也是 transform 中的一个属性问题(即同旋转)。
轮播 / 全屏
可以将图片轮播理解为横向滚动,全屏滚动理解为纵向滚动。
图片轮播的原理分为两种:(以 ABCD 4张图片为例)
第一种方案:
是将垂直方向上的轮播,可以理解为类比 PS 图层重叠,如下:
A 展示
B 隐藏
C 隐藏
D 隐藏
利用JS 控制图片的显隐,CSS3控制“障眼法”,主要是translate(移位)+ animation (动画)。
这种可以适用于轮播每次只展示完整一张图片的交互设计。
第二种方案:
如同视觉上的轮播,将图片横向排列 如 [ A B ] C D(方框内的,表示现在正在展示的)
- 给整个轮播外层Div固定一个宽度,超出隐藏
- 每次切换图片都是最左的图片左偏移(或者是最右的图片右偏移),然后加上CSS3 动画特效。
- 当来到最后一张图片时(即D),在D后面实际上有一张 A 的备份图,用于障眼法。
- 正常的动画继续从D到A,在到A(备份)的时候,把图片的所有的左偏移值置为0,即相当于此时回到了真正的第一张图片,且用户无感知(此时的切换不用加动画特效)。
- 然后代码会继续从第一张切到第二张等,达到无缝切换的效果。
- 从第一张 A 到 最后一张 D 时 同理
- 综上,采用这种的轮播实际上的图片列表是 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…)
懒加载不建议自己编写,常用的库有:
预加载
预加载除了会让交互体验更为友好外,还有一个用途是处理图片的闪动:加载背景图初次显示会闪一下的情况,可以使用预加载方法解决。
主要原理是利用浏览器的缓存机制(链接一样,浏览器就会调用缓存),有2种常用方案:
- 最简单,提前在CSS中 用 backage-image 加载图片,但是不要展示(比如隐藏、放到视区屏幕外等)
注意:Opera 和 Firefox 对 display:none 的元素的背景,不会立即发生请求,只有当其 display != none 才会发起图片请求,其他浏览器则是立即发起请求。
- 直接使用 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库已经挺多,比如:
其他
防盗链
同登录校验等逻辑,实际上都是服务端校验请求头上的参数,决定是否允许返回该图片资源。在图片上,最常用的是 referer 头。常用思路是:
- 当 referer 为空时, 返回正确的图
- 当 referer 不为空, 且 host 命中白名单网站时, 返回正确的图
- 当 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 端基本不建议再使用(但没有完全淘汰,图像领域、游戏领域还在使用),原因如下:
- 网页中目前用小图片素材已经比较少了,iconfont 的出现,大大减少了雪碧图的使用场景。
- 雪碧图的出现是为了解决很多小图片带来的很多额外的请求,不得已而为之的办法,因为其本身给设计和开发都带来了额外的成本,那些小图标位置确定了之后就不能随便改了,大小也不能随便改,不能动到其它图标的位置,有较多局限。
- 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 是互联网广告或网站监测方面常用的手段,简单、安全,原因如下:
- 避免跨域( img 元素原生支持跨域);
- 图片请求不占用 Ajax 请求限额;
- GIF 的最低合法体积最小(最小的 BMP 文件需要74个字节,PNG需要67个字节,而合法的 GIF,只需要43个字节)
- 相比 PNG/JPG 体积小,1px 透明图,对网页内容的影响几乎没有影响;
- 不会阻塞页面加载,影响用户的体验,只要 new Image 对象(异步),一般情况下不需要 append 到 DOM 中(存在内存中),通过它的 onerror 和 onload 事件来检测发送状态。
注意:无图模式问题下,图片不会发出请求的,即不会 onload、onerror 事件都不会触发,因此不推荐使用这种方式去发页面请求逻辑。
Canvas 绘图
使用 Canvas 绘图遇到一片黑或者一片白的问题,有三种可能
- 图片跨域
- 没在image.onload 函数中再调用绘图函数
- 没有调用 darwImage 等API 真正绘图
跨域问题:
Canvas 跨域问题其实有两种不同的情况:
第一种:可以拿到图片
可以正常的通过 Canvas 渲染,但是对其进行操作,如 toDataURL、getImageData 等操作时会提示跨域,对于这种情况,解决方案是:
可以通过Ajax请求,去获取到本图片,然后转文件流后再使用 Canvas 进行操作。
第二种:图片无法访问
通过调用 new Image、AJax 都无法访问,此时是图片还未下载就已经提示跨域,即还未进入到Canvas任何操作,对于这种情况,暂时没有纯前端技术上的解决方案。
目前的方案有三种:
- 是用户主动关闭浏览器的跨域拦截;
- 需要服务端将该图片转成非跨域的资源:如 base64、文件流、上传到CDN等;
- 是交互上提示用户先将该图片下载,然后通过将该文件上传,Canvas 再处理本地文件流,从而解决问题。
结尾
因为本文并非所有方案都亲身用于在项目中,难免会理解不到位,如有错误,还请帮忙指出~ Thanks♪(・ω・)ノ😊。
最后希望此文能帮上在前端图片上有困难的同学~