当前位置: 首页 > 科技观察

网页端实现自定义截图(原生JS版)

时间:2023-03-20 13:24:34 科技观察

本文转载自微信公众号《神奇程序员K》,作者神奇程序员K,转载请联系神奇程序员K公众号。前言前几天在网上发布了一个自定义截图的插件。在使用过程中,有开发者反映这个插件无法在vue2项目中使用,于是我开始查找问题,发现我的插件是基于Vue3开发的。是的,由于Vue3插件与Vue2插件完全不兼容,插件只能在Vue3项目中使用。经过一番考虑,我决定用原生js重构这个插件,让它不依赖任何库,让它可以运行在任何支持js的设备上。本文将与大家分享我是如何重构这个插件的,欢迎感兴趣的开发者阅读本文。运行结果视频:(请看原文)使用Vue实现Web端自定义截屏,效果如视频所示,文章、教程、体验地址明天分享给大家[坏笑]#Vue#打屏#自定义截图#Web前端写作在之前的文章中,我并没有讲解插件的具体实现思路。对插件实现感兴趣的开发者,请移步:Web端实现自定义截图搭建开发环境。我想使用ts、scss、eslint、prettier来提高插件的可维护性,觉得麻烦又不想手动配置webpack环境,所以决定使用VueCLI搭建插件开发环境。本文不详细介绍VueCLI搭建插件开发环境的过程。对此感兴趣的开发者请移步:DevelopaVue3npmlibraryusingCLI。移除vue相关依赖当我们搭建好插件开发环境后,CLI会默认在package.json中添加Vue相关的包。我们的插件是不会依赖vue的,所以可以删掉。{-"vue":"^3.0.0-0",-"vue-class-component":"^8.0.0-0"}创建DOM为了方便开发者使用dom,这里选择使用js动态创建dom,最后将其安装到主体上。在vue3版本的截图插件中,我们可以使用vue组件来辅助我们。这里我们需要使用js根据组件创建对应的dom,并绑定对应的事件。部分实现代码如下,完整代码请移步:CreateDom.tsimporttoolbarfrom"@/lib/config/Toolbar";import{toolbarType}from"@/lib/type/ComponentType";import{toolClickEvent}from"@/lib/split-methods/ToolClickEvent";导入{setBrushSize}来自"@/lib/common-methords/SetBrushSize";导入{selectColor}来自"@/lib/common-methords/SelectColor";导入{getColor}来自"@/lib/common-methords/GetColor";exportdefaultclassCreateDom{//截图区域画布容器privatereadonlyscreenShortController:HTMLCanvasElement;//截图工具栏容器privatereadonlytoolController:HTMLDivElement;//绘图选项topico容器privatereadonlyoptionIcoController:HTMLDivElement;//画笔绘图选项容器privatereadonlyoptionController:HTML//文本工具输入容器privatereadonlytextInputController:HTMLDivElement;//截图工具栏图标privatereadonlytoolbar:Array;constructor(){this.screenShortController=document.createElement("canvas");this.toolController=document.createElement("div");这个。可选的oController=document.createElement("div");this.optionController=document.createElement("div");this.textInputController=document.createElement("div");//为所有dom设置idthis.setAllControllerId();//Setclassthis.setOptionIcoClassName();this.toolbar=toolbarforbrushdrawingoptioncornermark;//rendertoolbarthis.setToolBarIco();//renderbrushrelatedoptionsthis.setBrushSelectPanel();//rendertextinputthis.setTextInputPanel();//渲染页面this.setDomToBody();//隐藏所有domthis.hiddenAllDom();}/**其他代码省略**/}插件入口文件在开发vue插件时,我们需要暴露一个install方法,由于我们这里不需要依赖vue,所以我们不需要暴露install方法。我的预期效果是:用户在使用我的插件时,可以直接实例化插件正常运行。所以,我们默认暴露一个类,无论是使用script标签导入插件,还是在其他js框架中使用import导入插件,使用时只需要使用new即可。部分代码如下,完整代码请移步:main.tsimportCreateDomfrom"@/lib/main-entrance/CreateDom";//导入截图导入所需的样式"@/assets/scss/screen-short。scss";importInitDatafrom"@/lib/main-entrance/InitData";import{cutOutBoxBorder,drawCutOutBoxReturnType,movePositionType,positionInfoType,zoomCutOutBoxReturnType}from"@/lib/type/ComponentType";import{drawMasking}from"@/lib/split-方法/DrawMasking”;从“@/lib/common-methords/FixedData”导入{fixedData,nonNegativeData};从“@/lib/split-methods/DrawPencil”导入{drawPencil,initPencil};从“@/lib/split-导入{drawText}方法/DrawText”;从“@/lib/split-methods/DrawRectangle”导入{drawRectangle};从“@/lib/split-methods/DrawCircle”导入{drawCircle};从“@/lib/split”导入{drawLineArrow}-methods/DrawLineArrow";导入{drawMosaic}来自"@/lib/split-methods/DrawMosaic";导入{drawCutOutBox}来自"@/lib/split-methods/DrawCutOutBox”;从“@/lib/common-methords/ZoomCutOutBoxPosition”导入{zoomCutOutBoxPosition};从“@/lib/common-methords/SaveBorderArrInfo”导入{saveBorderArrInfo};从“@/lib/split-methods导入{calculateToolLocation}/CalculateToolLocation";exportdefaultclassScreenShort{//当前实例的响应数据dataprivatereadonlydata:InitData;//视频容器用于存储屏幕MediaStream流privatereadonlyvideoController:HTMLVideoElement;//截图区域canvas容器privatereadonlyscreenShortController:HTMLCanvasElement|null;//截图工具栏domprivatereadonlytoolController:HTMLDivElement|null;//截图图片存储容器privatereadonlyscreenShortImageController:HTMLCanvasElement;//截图区域画布privatescreenShortCanvas:CanvasRenderingContext2D|undefined;//文字区域domprivatereadonlytextInputController:HTMLDivElement|null;//截图工具栏画笔选项doprivateoptionIcoController:HTMLDivElement|null;//图形位置参数privatedrawGraphPosition:positionInfoType={startX:0,startY:0,width:0,height:0};//临时图形位置参数NumberprivatetempGraphPosition:positionInfoType={startX:0,startY:0,width:0,height:0};//裁剪框边框节点坐标事件privatecutOutBoxBorderArr:Array=[];//当前操作的边框节点privateborderOption:number|null=null;//点击裁剪框时的鼠标坐标privatemovePosition:movePositionType={moveStartX:0,moveStartY:0};//鼠标点击状态privateclickFlag=false;privatefontSize=17;//最大可撤销次数privatemaxUndoNum=15;//马赛克涂抹区域的大小privatedegreeOfBlur=5;//文本输入框的位置privatetextInputPosition:{mouseX:number;mouseY:number}={mouseX:0,mouseY:0};constructor(){//CreatedomnewCreateDom();this.videoController=document.createElement("video");this.videoController.autoplay=true;this.screenShortImageController=document.createElement("canvas");//实例化响应式数据this.data=newInitData();//获取截图区域画布容器this.screenShortController=this.data.getScreenShortController()asHTMLCanvasElement|null;this.toolController=this.data.getToolController()asHTMLDivElement|null;this.textInputController=this.data.getTextInputController()asHTMLDivElement|null;this.optionController=this.data.getOptionController()asHTMLDivElement|null;this.optionIcoController=this.data.getOptionIcoController()asHTMLDivElement|null;this.load();}/**其他代码省略**/}对外暴露default属性完成以上配置后,我们的插件开发环境就搭建好了。我执行build命令打包插件后,在vue2项目中的import表单中运行正常,但是使用script标签的时候报错,所以我把暴露的screenShotPlugin变量打印出来后,发现也有默认属性。默认属性是我们的插件公开的内容。我的朋友@_Dreams找到了解决方案。我需要在webpack中配置output.libraryExport属性,我们的插件是使用VueCLI开发的,webpack的配置需要在vue.config.js中进行配置,代码如下:module.exports={//自定义webpack配置configureWebpack:{output:{//暴露默认属性libraryExport:"default"}}}的配置在VueCLI文档中也有提到。有兴趣的开发者请前往:build-targets.html#vue-vs-js-ts-entry-files使用webrtc截屏。插件首先使用html2canvas将dom转canvas,因为要遍历整个body中的dom再转canvas,图片不能跨域。如果页面中的图片很多,就会变得很慢。在上一篇文章的评论区,一个开发者@name不重要建议我用webrtc代替html2canvas,于是我看了webrtc的相关文档,终于实现了截屏功能,它截取的是更多准确并具有更好的性能。不存在卡顿问题和css问题,而且把选择权给了用户,让用户自己决定分享屏幕的哪一部分。实现思路下面给大家分享下我的实现思路:使用getDisplayMedia截屏并获取MediaStream流将获取到的MediaStream流输出到video标签中使用canvas将video标签中的内容绘制到canvas容器中具体关于getDisplayMedia的使用请移步:使用截屏API实现代码接下来我们看具体的实现代码,完整代码请移步:main.ts//加载截屏组件privateload(){//set截图区域的宽高canvasthis.data.setScreenShortInfo(window.innerWidth,window.innerHeight);//设置截图图片存储容器的宽高this.screenShortImageController.width=window.innerWidth;this.screenShortImageController.height=window.innerHeight;//显示截图区域容器this.data.showScreenShortPanel();//截取整个屏幕this.screenShot();}//开始截屏privatestartCapture=async()=>{letcaptureStream=null;try{//eslint-disable-next-line@typescript-eslint/ban-ts-ignore//@ts-ignore//截屏captureStream=awaitnavigator.mediaDevices.getDisplayMedia();//输出MediaStream到video标签this.videoController.srcObject=captureStream;}catch(err){throw"浏览器不支持webrtc"+err;}returncaptureStream;};//停止截屏privatestopCapture=()=>{constsrcObject=this.videoController.srcObject;if(srcObject&&"getTracks"insrcObject){consttracks=srcObject.getTracks();tracks.forEach(track=>track.stop());this.videoController.srcObject=null;}};//截图privatescreenShot=()=>{//开始截屏this.startCapture().then(()=>{setTimeout(()=>{//获取截屏区域canvas容器canvasconstcontext=this.screenShortController?.getContext("2d");if(context==null||this.screenShortController==null)return;//分配截图区域canvascanvasthis.screenShortCanvas=context;//绘制Masking层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