实现Web端自定义截屏(原生JS版)

简介: 实现Web端自定义截屏(原生JS版)

前言


前几天我发布了一个web端自定义截图的插件,在使用过程中有开发者反馈这个插件无法在vue2项目中使用,于是,我就开始找问题,发现我的插件是基于Vue3的开发的,由于Vue3的插件和Vue2的插件完全不兼容,因此插件也就只能在Vue3项目中使用。

经过一番考虑后,我决定用原生js来重构这个插件,让其不依赖任何库,这样它就能运行在任意一台支持js的设备上,本文就跟大家分享下我重构这个插件的过程,欢迎各位感兴趣的开发者阅读本文。


写在前面


本文不讲解插件的具体实现思路,对插件实现思路感兴趣的开发者请移步:实现Web端自定义截屏


搭建开发环境


我想使用ts、scss、eslint、prettier来提升插件的可维护性,又嫌麻烦,不想手动配置webpack环境,于是我决定使用Vue CLI来搭建插件开发环境。


本文不细讲Vue CLI搭建插件开发环境的过程,对此感兴趣的开发者请移步:使用CLI开发一个Vue3的npm库。


移除vue相关依赖


我们搭建好插件的开发环境后,CLI默认会在package.json中添加Vue的相关包,我们的插件不会依赖于vue,因此我们把它删除即可。


{
- "vue": "^3.0.0-0",
- "vue-class-component": "^8.0.0-0"
}


创建DOM


为了方便开发者使用dom,这里选择使用js动态来创建dom,最后将其挂载到body中,在vue3版本的截图插件中,我们可以使用vue组件来辅助我们,这里我们就要基于组件来使用js来创建对应的dom,为其绑定对应的事件。


部分实现代码如下,完整代码请移步:CreateDom.ts


import toolbar from "@/lib/config/Toolbar";
import { toolbarType } from "@/lib/type/ComponentType";
import { toolClickEvent } from "@/lib/split-methods/ToolClickEvent";
import { setBrushSize } from "@/lib/common-methords/SetBrushSize";
import { selectColor } from "@/lib/common-methords/SelectColor";
import { getColor } from "@/lib/common-methords/GetColor";
export default class CreateDom {
  // 截图区域canvas容器
  private readonly screenShortController: HTMLCanvasElement;
  // 截图工具栏容器
  private readonly toolController: HTMLDivElement;
  // 绘制选项顶部ico容器
  private readonly optionIcoController: HTMLDivElement;
  // 画笔绘制选项容器
  private readonly optionController: HTMLDivElement;
  // 文字工具输入容器
  private readonly textInputController: HTMLDivElement;
  // 截图工具栏图标
  private readonly toolbar: Array<toolbarType>;
    constructor() {
    this.screenShortController = document.createElement("canvas");
    this.toolController = document.createElement("div");
    this.optionIcoController = document.createElement("div");
    this.optionController = document.createElement("div");
    this.textInputController = document.createElement("div");
    // 为所有dom设置id
    this.setAllControllerId();
    // 为画笔绘制选项角标设置class
    this.setOptionIcoClassName();
    this.toolbar = toolbar;
    // 渲染工具栏
    this.setToolBarIco();
    // 渲染画笔相关选项
    this.setBrushSelectPanel();
    // 渲染文本输入
    this.setTextInputPanel();
    // 渲染页面
    this.setDomToBody();
    // 隐藏所有dom
    this.hiddenAllDom();
  }
  /** 其他代码省略 **/
}


插件入口文件


在开发vue插件时我们需要暴露一个install方法,由于此处我们不需要依赖vue,我们就无需暴露install方法,我的预想效果是:用户在使用我插件时,直接实例化插件就能正常运行。


因此,我们默认暴露出一个class,无论是使用script标签引入插件,还是在其他js框架里使用import来引入插件,都只需要在使用时new一下即可。


部分代码如下,完整代码请移步:main.ts


import CreateDom from "@/lib/main-entrance/CreateDom";
// 导入截图所需样式
import "@/assets/scss/screen-short.scss";
import InitData from "@/lib/main-entrance/InitData";
import {
  cutOutBoxBorder,
  drawCutOutBoxReturnType,
  movePositionType,
  positionInfoType,
  zoomCutOutBoxReturnType
} from "@/lib/type/ComponentType";
import { drawMasking } from "@/lib/split-methods/DrawMasking";
import { fixedData, nonNegativeData } from "@/lib/common-methords/FixedData";
import { drawPencil, initPencil } from "@/lib/split-methods/DrawPencil";
import { drawText } from "@/lib/split-methods/DrawText";
import { drawRectangle } from "@/lib/split-methods/DrawRectangle";
import { drawCircle } from "@/lib/split-methods/DrawCircle";
import { drawLineArrow } from "@/lib/split-methods/DrawLineArrow";
import { drawMosaic } from "@/lib/split-methods/DrawMosaic";
import { drawCutOutBox } from "@/lib/split-methods/DrawCutOutBox";
import { zoomCutOutBoxPosition } from "@/lib/common-methords/ZoomCutOutBoxPosition";
import { saveBorderArrInfo } from "@/lib/common-methords/SaveBorderArrInfo";
import { calculateToolLocation } from "@/lib/split-methods/CalculateToolLocation";
export default class ScreenShort {
  // 当前实例的响应式data数据
  private readonly data: InitData;
  // video容器用于存放屏幕MediaStream流
  private readonly videoController: HTMLVideoElement;
  // 截图区域canvas容器
  private readonly screenShortController: HTMLCanvasElement | null;
  // 截图工具栏dom
  private readonly toolController: HTMLDivElement | null;
  // 截图图片存放容器
  private readonly screenShortImageController: HTMLCanvasElement;
  // 截图区域画布
  private screenShortCanvas: CanvasRenderingContext2D | undefined;
  // 文本区域dom
  private readonly textInputController: HTMLDivElement | null;
  //  截图工具栏画笔选项dom
  private optionController: HTMLDivElement | null;
  private optionIcoController: HTMLDivElement | null;
  // 图形位置参数
  private drawGraphPosition: positionInfoType = {
    startX: 0,
    startY: 0,
    width: 0,
    height: 0
  };
  // 临时图形位置参数
  private tempGraphPosition: positionInfoType = {
    startX: 0,
    startY: 0,
    width: 0,
    height: 0
  };
  // 裁剪框边框节点坐标事件
  private cutOutBoxBorderArr: Array<cutOutBoxBorder> = [];
  // 当前操作的边框节点
  private borderOption: number | null = null;
  // 点击裁剪框时的鼠标坐标
  private movePosition: movePositionType = {
    moveStartX: 0,
    moveStartY: 0
  };
  // 鼠标点击状态
  private clickFlag = false;
  private fontSize = 17;
  // 最大可撤销次数
  private maxUndoNum = 15;
  // 马赛克涂抹区域大小
  private degreeOfBlur = 5;
  // 文本输入框位置
  private textInputPosition: { mouseX: number; mouseY: number } = {
    mouseX: 0,
    mouseY: 0
  };
  constructor() {
    // 创建dom
    new CreateDom();
    this.videoController = document.createElement("video");
    this.videoController.autoplay = true;
    this.screenShortImageController = document.createElement("canvas");
    // 实例化响应式data
    this.data = new InitData();
    // 获取截图区域canvas容器
    this.screenShortController = this.data.getScreenShortController() as HTMLCanvasElement | null;
    this.toolController = this.data.getToolController() as HTMLDivElement | null;
    this.textInputController = this.data.getTextInputController() as HTMLDivElement | null;
    this.optionController = this.data.getOptionController() as HTMLDivElement | null;
    this.optionIcoController = this.data.getOptionIcoController() as HTMLDivElement | null;
    this.load();
  }
  /** 其他代码省略 **/
}


对外暴露default属性


做完上述配置后我们的插件开发环境就搭建好了,我执行build命令打包插件后,在vue2项目中使用import形式正常运行,在使用script标签时引入时却报错了,于是我将暴露出来的screenShotPlugin变量打印出来后发现他还有个default属性,default属性才是我们插件暴露出来的东西。


求助了下我朋友@_Dreams找到了解决方案,需要配置下webpack中的output.libraryExport属性,我们的插件是使用Vue CLI开发的,有关webpack的配置需要在需要在vue.config.js中进行配置,代码如下:


module.exports = {
    // 自定义webpack配置
  configureWebpack: {
    output: {
      // 对外暴露default属性
      libraryExport: "default"
    }
  }
}


这一块的配置在Vue CLI文档中也有被提到,感兴趣的开发者请移步:build-targets.html#vue-vs-js-ts-entry-files


使用webrtc截取整个屏幕


插件一开始使用的是html2canvas来将dom转换为canvas的,因为他要遍历整个body中的dom,然后再转换成canvas,而且图片还不能跨域,如果页面中图片一多,它会变得非常慢。


在上一篇文章的评论区中有位开发者 @名字什么的都不重要 建议我使用webrtc来替代html2canvas,于是我就看了下webrtc的相关文档,最终实现了截屏功能,它截取出来的东西更精确、性能更好,不存在卡顿问题也不存在css问题,而且它把选择权交给了用户,让用户决定来共享屏幕的那一部分内容。


实现思路


接下来就跟大家分享下我的实现思路:


  • 使用getDisplayMedia来捕获屏幕,得到MediaStream
  • 将得到的MediaStream流输出到video标签中
  • 使用canvas将video标签中的内容绘制到canvas容器中

有关getDisplayMedia的具体用法,请移步:使用屏幕捕获API


实现代码


接下来,我们来看下具体的实现代码,完整代码请移步:main.ts


// 加载截图组件
  private load() {
    // 设置截图区域canvas宽高
    this.data.setScreenShortInfo(window.innerWidth, window.innerHeight);
    // 设置截图图片存放容器宽高
    this.screenShortImageController.width = window.innerWidth;
    this.screenShortImageController.height = window.innerHeight;
    // 显示截图区域容器
    this.data.showScreenShortPanel();
    // 截取整个屏幕
    this.screenShot();
  }
  // 开始捕捉屏幕
  private startCapture = async () => {
    let captureStream = null;
    try {
      // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
      // @ts-ignore
      // 捕获屏幕
      captureStream = await navigator.mediaDevices.getDisplayMedia();
      // 将MediaStream输出至video标签
      this.videoController.srcObject = captureStream;
    } catch (err) {
      throw "浏览器不支持webrtc" + err;
    }
    return captureStream;
  };
  // 停止捕捉屏幕
  private stopCapture = () => {
    const srcObject = this.videoController.srcObject;
    if (srcObject && "getTracks" in srcObject) {
      const tracks = srcObject.getTracks();
      tracks.forEach(track => track.stop());
      this.videoController.srcObject = null;
    }
  };
  // 截屏
  private screenShot = () => {
    // 开始捕捉屏幕
    this.startCapture().then(() => {
      setTimeout(() => {
        // 获取截图区域canvas容器画布
        const context = this.screenShortController?.getContext("2d");
        if (context == null || this.screenShortController == null) return;
        // 赋值截图区域canvas画布
        this.screenShortCanvas = context;
        // 绘制蒙层
        drawMasking(context);
        // 将获取到的屏幕截图绘制到图片容器里
        this.screenShortImageController
          .getContext("2d")
          ?.drawImage(
            this.videoController,
            0,
            0,
            this.screenShortImageController?.width,
            this.screenShortImageController?.height
          );
        // 添加监听
        this.screenShortController?.addEventListener(
          "mousedown",
          this.mouseDownEvent
        );
        this.screenShortController?.addEventListener(
          "mousemove",
          this.mouseMoveEvent
        );
        this.screenShortController?.addEventListener(
          "mouseup",
          this.mouseUpEvent
        );
        // 停止捕捉屏幕
        this.stopCapture();
      }, 300);
    });
  };


插件地址


至此,插件的实现过程就分享完毕了。


  • 插件在线体验地址:chat-system
  • 插件GitHub仓库地址:screen-shot
  • 开源项目地址:chat-system-github


写在最后


  • 公众号无法外链,如果文中有链接,可点击下方阅读原文查看😊
相关文章
|
28天前
|
JavaScript
使用Node.js创建一个简单的Web服务器
使用Node.js创建一个简单的Web服务器
|
1月前
|
机器学习/深度学习 人工智能 JavaScript
JavaScript和TypeScript的未来发展趋势及其在Web开发中的应用前景
本文探讨了JavaScript和TypeScript的未来发展趋势及其在Web开发中的应用前景。JavaScript将注重性能优化、跨平台开发、AI融合及WebAssembly整合;TypeScript则强调与框架整合、强类型检查、前端工程化及WebAssembly的深度结合。两者结合发展,特别是在Vue 3.0中完全采用TypeScript编写,预示着未来的Web开发将更加高效、可靠。
42 4
|
1月前
|
开发框架 JavaScript 前端开发
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势。通过明确的类型定义,TypeScript 能够在编码阶段发现潜在错误,提高代码质量;支持组件的清晰定义与复用,增强代码的可维护性;与 React、Vue 等框架结合,提供更佳的开发体验;适用于大型项目,优化代码结构和性能。随着 Web 技术的发展,TypeScript 的应用前景广阔,将继续引领 Web 开发的新趋势。
38 2
|
2月前
|
XML JavaScript 前端开发
JavaScript控制台:提升Web开发技能的秘密武器
作为Web开发人员,掌握JavaScript控制台中的各种方法至关重要。本文介绍了22种实用的console方法,从基本的log()到高级的profile()和memory,每种方法都配有示例和说明,帮助开发者更高效地调试和记录信息。通过了解这些工具,您可以优化代码、提高开发速度,减少错误,使编码过程更加顺畅愉快。
49 1
JavaScript控制台:提升Web开发技能的秘密武器
|
1月前
|
移动开发 前端开发 JavaScript
前端实训,刚入门,我用原生技术(H5、C3、JS、JQ)手写【网易游戏】页面特效
于辰在大学期间带领团队参考网易游戏官网的部分游戏页面,开发了一系列前端实训作品。项目包括首页、2021校园招聘页面和明日之后游戏页面,涉及多种特效实现,如动态图片切换和人物聚合效果。作品源码已上传至CSDN,视频效果可在CSDN预览。
45 0
|
1月前
|
JavaScript 前端开发 持续交付
构建现代Web应用:Vue.js与Node.js的完美结合
【10月更文挑战第22天】随着互联网技术的快速发展,Web应用已经成为了人们日常生活和工作的重要组成部分。前端技术和后端技术的不断创新,为Web应用的构建提供了更多可能。在本篇文章中,我们将探讨Vue.js和Node.js这两大热门技术如何完美结合,构建现代Web应用。
41 4
|
2月前
|
设计模式 JavaScript 前端开发
浅谈JavaScript 框架在现代 Web 开发中的作用
浅谈JavaScript 框架在现代 Web 开发中的作用
51 12
|
2月前
|
存储 JavaScript 前端开发
深入探索 Vue.js:构建现代 Web 应用的利器
【10月更文挑战第11天】深入探索 Vue.js:构建现代 Web 应用的利器
39 1
|
2月前
|
JavaScript 前端开发 网络架构
如何使用Vue.js构建响应式Web应用
【10月更文挑战第9天】如何使用Vue.js构建响应式Web应用
|
2月前
|
移动开发 JavaScript 前端开发
原生js如何获取dom元素的自定义属性
原生js如何获取dom元素的自定义属性
84 4