最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 实现Web端自定义截屏(原生JS版)

    正文概述 掘金(神奇的程序员)   2021-02-11   455

    前言

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

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

    运行结果视频:实现web端自定义截屏

    写在前面

    本文不讲解插件的具体实现思路,对插件实现思路感兴趣的开发者请移步:实现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"
        }
      }
    }
    

    使用webrtc截取整个屏幕

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

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

    实现思路

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

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

    实现代码

    接下来,我们来看下具体的实现代码,完整代码请移步: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

    写在最后

    • 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注?
    • 本文首发于掘金,未经许可禁止转载?

    起源地下载网 » 实现Web端自定义截屏(原生JS版)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元