Javascript保存网页元素为图片并下载到本地

背景

最近开发了一个应用包管理平台,主要提供给内部人员使用,可以扫码下载App安装包,因为还要平台外的分发,所以需要提供个二维码保存的功能。

分析

针对网页元素保存为图片,我可以用的是html2canvas,二维码生成我原来用的qrcode-react 库,但是发现这个图无法绘制到Canvas中,且这个库没有提供生成图片的回调,因此要实现我的功能只能二次开发下这个库了。

开始

生成二维码并回传图片信息

建一个二维码处理组件,可以看到依赖qr.js:

npm install qr.js

安装好后,创建文件 qrcode-reactjs.js

'use strict'

var React = require('react');
var PropTypes = require('prop-types');
var ReactDOM = require('react-dom');
var qr = require('qr.js');

function getBackingStorePixelRatio(ctx) {
  return (
    ctx.webkitBackingStorePixelRatio ||
    ctx.mozBackingStorePixelRatio ||
    ctx.msBackingStorePixelRatio ||
    ctx.oBackingStorePixelRatio ||
    ctx.backingStorePixelRatio ||
    1
  );
}

var getDOMNode;
if (/^0\.14/.test(React.version)) {
  getDOMNode = function(ref) {
    return ref;
  }
} else {
  getDOMNode = function(ref) {
    return ReactDOM.findDOMNode(ref);
  }
}

class QRCode extends React.Component {
  shouldComponentUpdate(nextProps) {
    var that = this;
    return Object.keys(QRCode.propTypes).some(function(k) {
      return that.props[k] !== nextProps[k];
    });
  }

  componentDidMount() {
    this.update();
  }

  componentDidUpdate() {
    this.update();
  }

  utf16to8(str) {
    var out, i, len, c;
    out = "";
    len = str.length;
    for (i = 0; i < len; i++) {
      c = str.charCodeAt(i);
      if ((c >= 0x0001) && (c <= 0x007F)) {
        out += str.charAt(i);
      } else if (c > 0x07FF) {
        out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F));
        out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F));
        out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
      } else {
        out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F));
        out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
      }
    }
    return out;
  }

  update() {
    var value = this.utf16to8(this.props.value);
    var qrcode = qr(value);
    var canvas = getDOMNode(this.refs.canvas);

    var ctx = canvas.getContext('2d');
    var cells = qrcode.modules;
    var tileW = this.props.size / cells.length;
    var tileH = this.props.size / cells.length;
    var scale = (window.devicePixelRatio || 1) / getBackingStorePixelRatio(ctx);
    canvas.height = canvas.width = this.props.size * scale;
    ctx.scale(scale, scale);

    cells.forEach(function(row, rdx) {
      row.forEach(function(cell, cdx) {
        ctx.fillStyle = cell ? this.props.fgColor : this.props.bgColor;
        var w = (Math.ceil((cdx + 1) * tileW) - Math.floor(cdx * tileW));
        var h = (Math.ceil((rdx + 1) * tileH) - Math.floor(rdx * tileH));
        ctx.fillRect(Math.round(cdx * tileW), Math.round(rdx * tileH), w, h);
      }, this);
    }, this);

    if (this.props.logo) {
      var self = this
      var size = this.props.size;
      var image = document.createElement('img');
      image.src = this.props.logo;
      image.onload = function() {
        var dwidth = self.props.logoWidth || size * 0.2;
        var dheight = self.props.logoHeight || image.height / image.width * dwidth;
        var dx = (size - dwidth) / 2;
        var dy = (size - dheight) / 2;
        image.width = dwidth;
        image.height = dheight;
        console.log('dwidth', dwidth)
        console.log('dheight', dheight)
        ctx.drawImage(image, dx, dy, dwidth, dheight);
      }
    }
    if (this.props.onCanvasLoad){
      this.props.onCanvasLoad(canvas.toDataURL('image/png'))
    }
  }

  render() {
    return React.createElement('canvas', {
      style: { height: this.props.size, width: this.props.size },
      height: this.props.size,
      width: this.props.size,
      ref: 'canvas',
    });
  }
}

QRCode.propTypes = {
  value: PropTypes.string.isRequired,
  size: PropTypes.number,
  bgColor: PropTypes.string,
  fgColor: PropTypes.string,
  logo: PropTypes.string,
  logoWidth: PropTypes.number,
  logoHeight: PropTypes.number,
  onCanvasLoad: PropTypes.func,
};

QRCode.defaultProps = {
  size: 128,
  bgColor: '#FFFFFF',
  fgColor: '#000000',
  value: 'http://facebook.github.io/react/'
};

module.exports = QRCode;

调用方式

import QRCode from '@/utils/qrcode-reactjs'

 this.handleImageLoad(dataUrl)} />

根据Base64内容创建图片Dom

这里做了个1秒延迟,防止二维码初始化时的报错,图片加载后注意把原来的QRCode节点隐藏。

handleImageLoad = dataUrl => {
    const { imgUrl } = this.state;
    if (imgUrl !== dataUrl) {
      setTimeout(() => {
        this.setState({
          imgUrl: dataUrl,
        })
      }, 1000)
    }
  }

{imgUrl && ( 
)}

保存图片到本地

保存元素到图片我用的html2canvas,支持保存整个页面,也支持保存指定的元素Dom。我们实际只需要二维码和下面的描述信息。所以直接给需要的元素加上ref即可,也可以给个id,自己查找下。

handleSaveImg = packageName => {
    html2canvas(this.qrCodeImg, { allowTaint: true, useCORS: true }).then(canvas => {
      const a = document.createElement('a');
      a.href = canvas.toDataURL('image/png');
      a.download = `${packageName}_${new Date().getTime().toString()}`;
      a.click();
    });
  }

{ this.qrCodeImg = node }} className={styles.flavorList} > ...

结语

至此功能已经完成了,需要的试试吧。

你可能感兴趣的:(Javascript保存网页元素为图片并下载到本地)