使用html2canvas制作一个截图工具

0 效果

1 下载html2canvas

npm install html2canvas --save

2 创建ClipScreen.js

javascript 复制代码
import html2canvas from 'html2canvas';
// 样式
const cssText = {
  box: 'overflow:hidden;position:fixed;left:0;top:0;right:0;bottom:0;background-color:rgba(255,255,255,0.9);z-index: 100000;',
  img: '',
  mask: 'position:absolute;left:0;top:0;width:100%;height:100%;background:rgba(0,0,0,0.6);',
  rect: 'position:absolute;border:1px solid #3e8ef7;box-sizing:border-box;cursor:move;user-select:none;background: url() no-repeat;',
  toolBox: 'position:absolute;top:0;left:0;padding:0 10px;background:#eee;line-height:2em;text-align:right;',
  toolBtn: 'font-weight:bold;color:#111;margin:0 1em;user-select:none;font-size:12px;cursor:pointer;',
}
/**
 * dom节点截图工具(基于html2canvas)
 * dom: 要截图的目标dom
 * options: { 
 *   // 以下三个回调方法作用域this指向构造函数
 *   success: function(res), //截图完成触发 参数为截图结果
 *   fail: function(), //取消截图触发
 *   complete: function(), //截图结束触发 success和fail都会触发
 * }
 * 
 * 调用示例:
 * new ClipScreen(dom节点, {
 *   success: function (res) {},
 *   complete: function () {},
 * });
 */
class ClipScreen {
  constructor(dom, options) {
    if (window.ClipScreen) return false;
    window.ClipScreen = this;
    this.dom = dom;
    this.options = options;
    html2canvas(this.dom, {useCORS: true}).then((canvas) => {
      let dataURL = canvas.toDataURL("image/png");
      this.imgUrl = dataURL;
      this.start();
    });
  }
  // 初始化
  start() {
    this.border = 2; //用于计算选区拖拽点和边界的判断
    this.win_w = window.innerWidth;
    this.win_h = window.innerHeight;
    let box = this.box = document.createElement('div');
    box.id = 'ClipScreen';
    box.style.cssText = cssText.box;
    let img = document.createElement('img');
    img.style.cssText = cssText.img;
    img.src = this.imgUrl;
    let mask = document.createElement('div');
    mask.style.cssText = cssText.mask;
    box.appendChild(img);
    box.appendChild(mask);
    document.body.appendChild(box);
    img.onload = (e) => {
      let w = img.offsetWidth,
        h = img.offsetHeight,
        win_w = window.innerWidth,
        win_h = window.innerHeight,
        left = (win_w - w) / 2,
        top = (win_h - h) / 2;
      img.style.position = 'absolute';
      img.style.left = left + 'px';
      img.style.top = top + 'px';
      img.style.width = w + 'px';
      img.style.height = h + 'px';
      this.axis = {
        left,
        top
      }
      this.img = img;
      this.bindEvent(mask);
    }
  }
  // 绑定蒙版事件、键盘事件
  bindEvent(mask) {
    document.onkeydown = (e) => {
      if (e.keyCode == 27) {
        this.cancel();
      }
    }
    mask.onmousedown = (e) => {
      let offsetX = e.offsetX,
        offsetY = e.offsetY;
      document.onmousemove = (e) => {
        let x = e.offsetX,
          y = e.offsetY,
          sx = offsetX,
          sy = offsetY,
          w = Math.abs(offsetX - x),
          h = Math.abs(offsetY - y);
        if (x < offsetX) sx = x;
        if (y < offsetY) sy = y;
        this.createRect(sx, sy, w, h);
      }
      document.onmouseup = (e) => {
        this.moveToolBox();
        this.rect.style.pointerEvents = 'initial';
        this.unbindMouseEvent();
      }
    }
  }
  // 创建矩形截图选区
  createRect(x, y, w, h) {
    let rect = this.rect;
    if (!rect) {
      rect = this.rect = document.createElement('div');
      rect.style.cssText = cssText.rect;
      rect.style.backgroundImage = 'url(' + this.imgUrl + ')';
      // this.newImg = document.createElement('img');
      // this.newImg.style.cssText = cssText.rect_img;
      // rect.appendChild(this.newImg);
      let doms = this.createPoints(rect);
      this.box.appendChild(rect);
      this.bindRectEvent(doms);
    }
    let border = this.border;
    if (x <= border) x = border;
    if (y <= border) y = border;
    if (x + w >= this.win_w - border) x = this.win_w - border - w;
    if (y + h >= this.win_h - border) y = this.win_h - border - h;
    rect.style.pointerEvents = 'none';
    rect.style.display = 'block';
    rect.style.left = x + 'px';
    rect.style.top = y + 'px';
    rect.style.width = w + 'px';
    rect.style.height = h + 'px';
    rect.style.backgroundPosition = (-x + this.axis.left - 1) + 'px ' + (-y + this.axis.top - 1) + 'px';
    if (this.toolBox) this.toolBox.style.display = 'none';
  }
  // 创建截图选区各个方位拉伸点
  createPoints(rect) {
    let
      lt = document.createElement('span'),
      tc = document.createElement('span'),
      rt = document.createElement('span'),
      rc = document.createElement('span'),
      rb = document.createElement('span'),
      bc = document.createElement('span'),
      lb = document.createElement('span'),
      lc = document.createElement('span');
    let c_style = 'position:absolute;width:5px;height:5px;background:#3e8ef7;';
    lt.style.cssText = c_style + 'left:-3px;top:-3px;cursor:nw-resize;';
    tc.style.cssText = c_style + 'left:50%;top:-3px;margin-left:-3px;cursor:ns-resize;';
    rt.style.cssText = c_style + 'right:-3px;top:-3px;cursor:ne-resize;';
    rc.style.cssText = c_style + 'top:50%;right:-3px;margin-top:-3px;cursor:ew-resize;';
    rb.style.cssText = c_style + 'right:-3px;bottom:-3px;cursor:nw-resize;';
    bc.style.cssText = c_style + 'left:50%;bottom:-3px;margin-left:-3px;cursor:ns-resize;';
    lb.style.cssText = c_style + 'left:-3px;bottom:-3px;cursor:ne-resize;';
    lc.style.cssText = c_style + 'top:50%;left:-3px;margin-top:-3px;cursor:ew-resize;';
    let res = {
      lt,
      tc,
      rt,
      rc,
      rb,
      bc,
      lb,
      lc
    }
    for (let k in res) {
      rect.appendChild(res[k])
    }
    res.rect = rect;
    return res;
  }
  // 生成 、移动工具
  moveToolBox() {
    let toolBox = this.toolBox;
    if (!toolBox) {
      toolBox = this.toolBox = document.createElement('div');
      toolBox.style.cssText = cssText.toolBox;
      let save = document.createElement('span'),
        cancel = document.createElement('span');
      save.innerText = '完成';
      cancel.innerText = '取消';
      save.style.cssText = cancel.style.cssText = cssText.toolBtn;
      toolBox.appendChild(cancel);
      toolBox.appendChild(save);
      this.box.appendChild(toolBox);
      this.bindToolBoxEvent(save, cancel);
    }
    toolBox.style.display = 'block';
    let border = this.border;
    let t_w = this.toolBox.offsetWidth,
      t_h = this.toolBox.offsetHeight,
      r_t = this.rect.offsetTop,
      r_h = this.rect.offsetHeight;
    let t = r_t + r_h + 10,
      l = this.rect.offsetLeft + this.rect.offsetWidth - t_w;
    if (l <= border) l = border;
    if (t >= this.win_h - border - t_h) t = r_t - t_h - 10;
    if (r_h >= this.win_h - border - t_h) {
      t = r_t + r_h - t_h - 10;
      l -= 10;
    }
    toolBox.style.top = t + 'px';
    toolBox.style.left = l + 'px';
  }
  // 绑定工具栏事件
  bindToolBoxEvent(save, cancel) {
    save.onclick = () => {
      this.success();
    }
    cancel.onclick = () => {
      this.cancel();
    }
  }
  // 绑定截图选区事件
  bindRectEvent(o) {
    o.rect.addEventListener("mousedown", (e) => {
      let border = this.border;
      let $target = e.target;
      let offsetX = e.x,
        offsetY = e.y;
      let r_w = o.rect.offsetWidth,
        r_h = o.rect.offsetHeight,
        r_l = o.rect.offsetLeft,
        r_t = o.rect.offsetTop;

      if ($target == o.rect) {
        offsetX = e.offsetX;
        offsetY = e.offsetY;
        document.onmousemove = (e) => {
          let dif_x = e.x - offsetX,
            dif_y = e.y - offsetY;
          if (dif_x <= border) dif_x = border;
          if (dif_y <= border) dif_y = border;
          if (dif_x + r_w >= this.win_w - border) dif_x = this.win_w - border - r_w;
          if (dif_y + r_h >= this.win_h - border) dif_y = this.win_h - border - r_h;
          o.rect.style.left = dif_x + 'px';
          o.rect.style.top = dif_y + 'px';
          o.rect.style.backgroundPosition = (-dif_x + this.axis.left - 1) + 'px ' + (-dif_y + this.axis.top - 1) + 'px';
          this.toolBox.style.display = 'none'
        }
      } else {
        document.onmousemove = (e) => {
          this.toolBox.style.display = 'none'
          this.transform($target, o, offsetX, offsetY, r_w, r_h, r_l, r_t, e)
        }
      }
      document.onmouseup = (e) => {
        this.moveToolBox();
        this.unbindMouseEvent();
      }
    })
  }
  // 拉伸选区
  transform($t, o, offsetX, offsetY, r_w, r_h, r_l, r_t, e) {
    let border = this.border;
    let x = e.x,
      y = e.y;
    if (x <= border) x = border;
    if (y <= border) y = border;
    if (x >= this.win_w - border) x = this.win_w - border;
    if (y >= this.win_h - border) y = this.win_h - border;
    let dif_x = x - offsetX,
      dif_y = y - offsetY;
    let min = 10;
    let left = r_l,
      top = r_t,
      width = r_w,
      height = r_h;
    if ($t == o.lt) {
      if (r_w - dif_x <= min || r_h - dif_y <= min) return false;
      left = r_l + dif_x;
      top = r_t + dif_y;
      width = r_w - dif_x;
      height = r_h - dif_y;
    } else if ($t == o.tc) {
      if (r_h - dif_y <= min) return false;
      top = r_t + dif_y;
      height = r_h - dif_y;
    } else if ($t == o.rt) {
      if (r_w + dif_x <= min || r_h - dif_y <= min) return false;
      top = r_t + dif_y;
      width = r_w + dif_x;
      height = r_h - dif_y;
    } else if ($t == o.rc) {
      if (r_w + dif_x <= min) return false;
      width = r_w + dif_x;
    } else if ($t == o.rb) {
      if (r_w + dif_x <= min || r_h + dif_y <= min) return false;
      width = r_w + dif_x;
      height = r_h + dif_y;
    } else if ($t == o.bc) {
      if (r_h + dif_y <= min) return false;
      height = r_h + dif_y;
    } else if ($t == o.lb) {
      if (r_w - dif_x <= min || r_h + dif_y <= min) return false;
      left = r_l + dif_x;
      width = r_w - dif_x;
      height = r_h + dif_y;
    } else if ($t == o.lc) {
      if (r_w - dif_x <= min) return false;
      left = r_l + dif_x;
      width = r_w - dif_x;
    }
    o.rect.style.left = left + 'px';
    o.rect.style.top = top + 'px';
    o.rect.style.width = width + 'px';
    o.rect.style.height = height + 'px';
    o.rect.style.backgroundPosition = (-left + this.axis.left - 1) + 'px ' + (-top + this.axis.top - 1) + 'px';
  }
  // 解绑事件
  unbindMouseEvent() {
    document.onmousemove = null;
    document.onmouseup = null;
  }
  // 生成base64图片
  getImagePortion(imgDom, new_w, new_h, s_x, s_y) {
    let sx = s_x - this.axis.left,
      sy = s_y - this.axis.top;
    let t_cv = document.createElement('canvas');
    let t_ct = t_cv.getContext('2d');
    t_cv.width = new_w;
    t_cv.height = new_h;

    let b_cv = document.createElement('canvas');
    let b_ct = b_cv.getContext('2d');
    b_cv.width = imgDom.width;
    b_cv.height = imgDom.height;
    b_ct.drawImage(imgDom, 0, 0);

    t_ct.drawImage(b_cv, sx, sy, new_w, new_h, 0, 0, new_w, new_h);
    let res = t_cv.toDataURL();
    return res;
  }
  // 完成
  success() {
    let imgBase64 = this.getImagePortion(this.img, this.rect.offsetWidth, this.rect.offsetHeight, this.rect.offsetLeft, this.rect.offsetTop);
    if (this.options) {
      this.options.success && this.options.success.call(this, imgBase64);
    }
    this.close();
  }
  // 取消
  cancel() {
    if (this.options) {
      this.options.fail && this.options.fail.call(this);
    }
    this.close();
  }
  // 关闭
  close() {
    if (this.options) {
      this.options.complete && this.options.complete.call(this);
    }
    this.distroy();
  }
  // 销毁
  distroy() {
    window.ClipScreen = undefined;
    this.box.remove();
  }
}
export default ClipScreen

3 使用

① 引入ClipScreen.js

② 获取需要截图的div

javascript 复制代码
  <div style="position: absolute; top: 0; left: 0; bottom: 0; right: 0;" ref="toImage">
    <button type="primary" size="mini" @click="screenshot">一键截图</button>
    <div style="background-color: red; margin-top: 20px;">
      <div style="height: 200px; width: 400px;">123</div>
      <div style="height: 200px; width: 400px;">123</div>
    </div>
  </div>
javascript 复制代码
    screenshot() {
      let canvasItem = this.$refs.toImage;
      new ClipScreen(canvasItem, {
        success: function(res) { // 完成截图
          let screenshotImage = document.createElement('a');
          screenshotImage.href = res;
          screenshotImage.download = '网页截图';
          screenshotImage.click();
        },
        fail: function() {}, // 取消截图
        complete: function(res) {} // 结束截图
      })
    }

4 其他

截取iframe内容时,会空白,暂时未解决。

也可以使用 js-web-screen-shot

相关推荐
长风清留扬10 分钟前
小程序毕业设计-音乐播放器+源码(可播放)下载即用
javascript·小程序·毕业设计·课程设计·毕设·音乐播放器
m0_7482478024 分钟前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
漫天转悠28 分钟前
VScode中配置ESlint+Prettier详细步骤(图文详情)
vscode·vue
ZJ_.1 小时前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
joan_851 小时前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
还是大剑师兰特2 小时前
什么是尾调用,使用尾调用有什么好处?
javascript·大剑师·尾调用
Watermelo6172 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
一个处女座的程序猿O(∩_∩)O4 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
燃先生._.10 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js