前端js实现div、svg下载成图片

5,845 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

hello,小伙伴们好,今天给大家分享一个不但十分有趣而且十分好玩的功能,那就是图片下载。怎么样,听起来是不是有那么点兴趣了,把自己写的div直接转成一张图片,听起来就很酷对不对?好的,话不多说,开始愉快的旅程吧!

前端js下载图片的原理是什么?

  1. 前端通过编码将svg和div转化为一串地址。
  2. 将地址赋给Image对象的src属性,就可以正常显示图片了。
  3. 通过canvas将image设置为jpg或png文件。
  4. 通过a链接触发下载。 没错,就这四步,让我们一步一步深入。

怎么将svg和div转化为一串地址?

想要将svg和div转化为地址,需要使用XMLSerializer,window.btoa,encodeURIComponent,unescape几个window原生api对字符串进行转码,先来一一介绍下这几个主角。

  • XMLSerializer 是window自带的api,作用就是将dom节点转化为xml字符串。
  • window.btoa 也是window自带的api,作用是将字符串转化为base64编码。
  • encodeURIComponent 用于编码 URI(统一资源标识符,是一个指向资源的字符串)。
  • unescape 将表现为%+16进制的字符串替换成对应的字符。 img的src一般是这样的:
src='data:image/svg+xml;base64,fjaiejfael.......(一段字符串)'  这是base64编码的图片地址

或者是这样的:

src='data:image/svg+xml;chartset=utf-8,fjaiejfael.......(一段字符串)' 这是utf-8编码的图片地址

接下来,我们先来学习如何生成img地址。话不多说直接上代码,先定义一段html。

<div class='wrap'>
    <svg width='400' height='400' class='down'>
      <foreignObject x='0'  y='0' height='400' width='400'>
          <div  style='background:red;height:100%'>
            有本事下载我啊
          </div>
      </foreignObject>
    </svg>
</div>
<button id='btn'>点击我下载</button>

foreignObject 用来向svg中插入div等dom元素):

效果如图:

微信图片_20220111164140.png

有了这段html,我们就可以生成img的src了,看具体代码:

代码一:

   const down=document.querySelector('.down');
   
   const downCopy=down.cloneNode(down);//克隆一份dom,避免样式设置等操作影响原有界面
   
   downCopy.children[0].children[0].style.background='skyblue';//设置div背景为蓝色
   
   const xmlDom=new XMLSerializer().serializeToString(downCopy);//将设置好颜色的div转为xml
   
   const xml=`<?xml version="1.0" standalone="no"?>\r\n${xmlDom}`;//生成xml文件
   
   const src=`data:image/svg+xml;chartset=utf-8,${encodeURIComponent(xml)}`;//生成src

代码二:

   const down=document.querySelector('.down');
   
   const downCopy=down.cloneNode(down);//克隆一份dom,避免样式设置等操作影响原有界面
   
   downCopy.children[0].children[0].style.background='skyblue';//设置div背景为蓝色
   
   const xmlDom=new XMLSerializer().serializeToString(downCopy);
   
   const xml=`<?xml version="1.0" standalone="no"?>\r\n${xmlDom}`;
   
   const src=`data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(xml)))}`;

由此,img的src就生成了。XMLSerializer对象下的serializeToString方法,参数是一个节点,返回值就是xml。上面两串代码唯一的不同点就是最后一句代码,一个是直接通过encodeURIComponent编码,而另一个是通过window.btoa转为了base64编码。现在,生成img src的过程已经结束了,也就意味着这个src已经可以用作页面展示了。看如下代码:

const img=new Image();
img.src=src;(这个src就是上面生成的src)
document.body.appendChild(img);

给button绑定点击事件,每点击一次我们就往body中加一个img图片。我点击了三次,效果如下:

微信图片_20220111165211.png

为什么要用canvas?

明明我们的img都生成了,那直接用a链接触发下载就行了,为什么还要用canvas?这不是多此一举吗?其实并没有,因为我们无法通过js将img设置成jpg或者png文件,只能通过canvas的toDataURL方法设置img的类型。

好的继续往下,看代码:

 const canvas = document.createElement('canvas');
 const canWidth=400,canHeight=400;
 canvas.width = canWidth;
 canvas.height = canHeight;
 
 const context = canvas.getContext('2d');
 
 img.onload =()=> {
  context.drawImage(img, 0, 0, canWidth, canHeight);
  const a = document.createElement('a');
  a.download = `下载.jpg`;
  a.href = canvas.toDataURL('image/jpeg', 1);
  a.click();
 };

以上代码首先创建了一个canvas,然后设置宽高为400,接下来获取context画图对象,再调用drawImage方法在canvas上画图片,然后通过toDataURL将图片导出生成链接,最后通过a链接触发下载,就可以下载图片了。toDataURL有两个参数,第一个就是设置图片类型有png,jpeg,webp三种选择,当选择为jpeg,webp时可以设置第二个参数表示图片的质量,值范围0-1;1就是超清,0就是超级模糊。

微信图片_20220101143232.jpg

最后调整代码,下载出的图片如下:

微信图片_20220111165943.png

下载时图片不清晰该怎么解决?

用这种方式下载图片会出现图片不清晰情况,这时,就需要根据图片宽高等比放大canvas的宽高,再配合toDataURL的第二个参数就可以获取高清图片。 为什么调整canvas的宽高图片会更清晰呢?看代码:

canvas.width=1200;

这样用js设置canvas的宽高,是在设置像素点,像素点的多少决定图片的清晰度,宽高越大,像素点越多,图片就会越清晰。好了,图片下载功能到这里就完结了,撒花!