UNPKG

js-web-screen-shot

Version:

web端自定义截屏插件(原生JS版)

497 lines (419 loc) 23.5 kB
# js-web-screen-shot · [![npm](https://img.shields.io/badge/npm-v2.0.2-2081C1)](https://www.npmjs.com/package/js-web-screen-shot) [![pnpm](https://img.shields.io/badge/pnpm-v2.0.2-F69220)](https://pnpm.io) [![github](https://img.shields.io/badge/GitHub-depositary-9A9A9A)](https://github.com/likaia/js-screen-shot) [![](https://img.shields.io/github/issues/likaia/js-screen-shot)](https://github.com/likaia/js-screen-shot/issues) [![]( https://img.shields.io/github/forks/likaia/js-screen-shot)](https://github.com/likaia/js-screen-shot/network/members) [![]( https://img.shields.io/github/stars/likaia/js-screen-shot)](https://github.com/likaia/js-screen-shot/stargazers) web端自定义截屏插件(原生JS版),运行视频:[实现web端自定义截屏功能](https://www.bilibili.com/video/BV1Ey4y127cV) ,效果图如下:![截屏效果图](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/486d810877a24582aa8cf110e643c138~tplv-k3u1fbpfcp-watermark.image) ## 写在前面 关于此插件的更多介绍以及实现原理请移步: - [实现Web端自定义截屏](https://juejin.cn/post/6924368956950052877) - [实现Web端自定义截屏(JS版)](https://juejin.cn/post/6931901091445473293) > 注意⚠️:本文档并非最新的,最新文档请移步[官网](https://www.kaisir.cn/js-screen-shot/) ## 插件安装 ```bash pnpm add js-web-screen-shot # or npm install js-web-screen-shot --save ``` ## 插件使用 由于插件采用原生js编写且不依赖任何第三方库,因此它可以在任意一台支持js的设备上运行。 > 注意⚠️: 如果需要使用插件的webrtc模式或者截图写入剪切板功能,需要你的网站运行在`https`环境或者`localhost`环境。当然,也可以通过修改浏览器设置的方式实现在所有环境下都能运行。步骤如下: > 1.打开谷歌浏览器,在地址栏输入`chrome://flags/#unsafely-treat-insecure-origin-as-secure` > 2.在打开的界面中:下拉框选择enabled,地址填写你的项目访问路径。 > ![img.png](https://www.kaisir.cn/uploads/MarkDownImg/20230531/5e49de8f32f54f8bb972b4f472d4272e.png) ### import形式使用插件 * 在需要使用截屏插件的业务代码中导入插件 ```javascript import ScreenShot from "js-web-screen-shot"; ``` * 在业务代码中使用时实例化插件即可 ```javascript new ScreenShot(); ``` > ⚠️注意:实例化插件时一定要等dom加载完成,否则插件无法正常工作。 ### cdn形式使用插件 * 将插件的`dist`文件夹复制到你的项目中 * 使用`script`标签引入dist目录下的`screenShotPlugin.umd.js`文件 ```javascript <script src="./screenShotPlugin.umd.js"></script> ``` * 在业务代码中使用时实例化插件即可 ```javascript // 截图确认按钮回调函数 const callback = ({base64, cutInfo})=>{ console.log(base64, cutInfo); } // 截图取消时的回调函数 const closeFn = ()=>{ console.log("截图窗口关闭"); } new screenShotPlugin({ capture: { source: "display-media" }, completeCallback: callback, closeCallback: closeFn }); ``` > ⚠️注意:实例化插件时一定要等dom加载完成,否则插件无法正常工作。 ### 推荐的 capture 配置`2.0.0` 开始,推荐优先使用 `capture` 来描述截图来源与渲染策略: ```typescript new ScreenShot({ capture: { source: "display-media", render: "browser-frame" } }); ``` `capture` 支持的配置如下: * `source` * `display-media` 浏览器原生屏幕捕获 * `injected-stream` 外部传入屏幕流,适合 `Electron` * `dom` 使用 `html2canvas` 渲染当前页面 * `snapdom` 使用 SnapDOM 渲染当前页面 * `image` 使用外部传入的图片内容 * `render` * `browser-frame` 当前标签页截图 * `window-frame` 窗口截图 * `cursor` * 屏幕捕获流是否包含鼠标指针,值为 `never | motion | always`,默认 `never` * 仅对 `source: "display-media"` 生效;`injected-stream` 模式下需要在创建外部流时自行处理 * `stream` *`source``injected-stream` 时必传 * `imageSrc` *`source``image` 时必传 * `snapdom` *`source``snapdom` 时可传,值为 SnapDOM 导出的 `snapdom` 对象;不传时会读取 `window.snapdom` * `snapdomOptions` *`source``snapdom` 时可传,值为 SnapDOM 的截图配置 > 旧参数 `enableWebRtc`、`screenFlow`、`imgSrc`、`wrcWindowMode` 仍然兼容,但已经进入废弃迁移阶段。新项目请直接使用 `capture`,旧参数将在后续版本中移除。 ### electron环境下使用插件 由于electron环境下无法直接调用webrtc来获取屏幕流,因此需要调用者自己稍作处理,具体做法如下所示: * 直接获取设备的窗口,主线程发送一个IPC消息handle ```javascript // electron主线程 import { desktopCapturer, webContents } from "electron"; // 修复electron18.0.0-beta.5 之后版本的BUG: 无法获取当前程序页面视频流 const selfWindws = async () => await Promise.all( webContents .getAllWebContents() .filter(item => { const win = BrowserWindow.fromWebContents(item); return win && win.isVisible(); }) .map(async item => { const win = BrowserWindow.fromWebContents(item); const thumbnail = await win?.capturePage(); // 当程序窗口打开DevTool的时候 也会计入 return { name: win?.getTitle() + (item.devToolsWebContents === null ? "" : "-dev"), // 给dev窗口加上后缀 id: win?.getMediaSourceId(), thumbnail, display_id: "", appIcon: null }; }) ); // 获取设备窗口信息 ipcMain.handle("IPC消息名称", async (_event, _args) => { return [ ...(await desktopCapturer.getSources({ types: ["window", "screen"] })), ...(await selfWindws()) ]; }); ``` * 渲染线程(前端)发送消息封装处理(相应写法自己调整) ```typescript // xxx.ts export const getDesktopCapturerSource = async () => { return await window.electron.ipcRenderer.invoke<Electron.DesktopCapturerSource[]>("IPC消息名称", []); } ``` * 获取指定窗口的媒体流 ```typescript // yyy.ts export function getInitStream(source: any): Promise<MediaStream | null> { return new Promise((resolve, _reject) => { // 获取指定窗口的媒体流 // 此处遵循的是webRTC的接口类型 暂时TS类型没有支持 只能断言成any (navigator.mediaDevices as any).getUserMedia({ audio: false, video: { mandatory: { chromeMediaSource: 'desktop', chromeMediaSourceId: source.id }, } }).then((stream: MediaStream) => { resolve(stream); }).catch((error: any) => { console.log(error); resolve(null); }) }); } ``` * 前端调用设备窗口信息 ```typescript import { getDesktopCapturerSource } from "xxx.ts"; import { getInitStream } from "yyy.ts"; import ScreenShot from "js-web-screen-shot"; export const doScreenShot = async ()=>{ // 下面这两块自己考虑 const sources = await getDesktopCapturerSource(); // 这里返回的是设备上的所有窗口信息 // 这里可以对`sources`数组下面id进行判断 找到当前的electron窗口 这里为了简单直接拿了第一个 const stream = await getInitStream(sources[0]); new ScreenShot({ capture: { source: "injected-stream", stream: stream! }, level: 999, }); } ``` > `capture.cursor` 只会影响浏览器原生 `display-media` 捕获。Electron 通过 `capture.source = "injected-stream"` 传入的流已经在外部创建,插件无法再从流里移除鼠标指针。 如果 Electron 截图底图里仍然出现鼠标指针,可以在调用侧用 `@nut-tree/nut-js` 做兜底:截图前保存当前鼠标位置并移出屏幕区域,等 `triggerCallback` 触发后再移回。这个逻辑需要放在 Electron 有 Node 能力的一侧执行,并按系统要求授予辅助功能/自动化权限。 ```typescript import ScreenShot from "js-web-screen-shot"; import { mouse, Point, straightTo } from "@nut-tree/nut-js"; import { getDesktopCapturerSource } from "xxx.ts"; import { getInitStream } from "yyy.ts"; export const doScreenShotWithoutCursor = async () => { const previousPosition = await mouse.getPosition(); await mouse.move(straightTo(new Point(-100, -100))); let cursorRestored = false; const restoreCursor = async () => { if (cursorRestored) return; cursorRestored = true; await mouse.move(straightTo(previousPosition)); }; const sources = await getDesktopCapturerSource(); const stream = await getInitStream(sources[0]); new ScreenShot({ capture: { source: "injected-stream", stream: stream! }, triggerCallback: restoreCursor, cancelCallback: restoreCursor, closeCallback: restoreCursor, level: 999 }); }; ``` 如果你的多屏坐标不允许负数位置,可以把 `new Point(-100, -100)` 换成主屏幕右下角外侧或项目里确认过的屏幕外坐标。 > 感谢 [@Vanisper](https://github.com/Vanisper) 提供的在electron环境下使用本插件的兼容思路。 ### 使用 electron 编写 Mac 软件 Mac 全屏场景下,系统菜单栏可能会覆盖截图工具栏。可以通过 `menuBarHeight` 设置菜单栏高度,插件在计算全屏工具栏位置时会扣除这部分高度。 ```typescript screenShotIns = new ScreenShot({ menuBarHeight: 22 }); ``` `menuBarHeight` 使用逻辑像素,常见 Mac 菜单栏高度是 `22`,Retina 屏幕视觉尺寸不变;如果开启系统缩放、辅助功能大字号,或使用刘海屏 MacBook,可以按实际项目微调到 `24` 或更高。 ### electron示例代码 如果你看完上个章节的使用方法,依然不是很理解的话,这里准备了一份在electron环境下使用本插件的demo,请移步[electron-js-web-screen-shot-demo](https://github.com/Vanisper/electron-js-web-screen-shot-demo)。 ### 兼容移动端 插件对触屏设备做了兼容处理,如果你是pc端的触屏设备可以支持webrtc模式,如果是移动端那么就只能使用html2canvas模式。 ```javascript import ScreenShot from "js-web-screen-shot"; const config = { capture: { source: "dom" } }; const screenShotHandler = new ScreenShot(config); ``` ```html <!DOCTYPE html> <html lang="zh-CN"> <head> <!--禁止移动端浏览器的缩放--> <meta name="viewport" content="user-scalable=no"> </head> <body> /body> </html> ``` > 注意:在移动端使用时,需要在head标签里禁止浏览器的缩放行为,否则就会出现在使用撤销功能时,多次双击造成界面放大问题。 ### Vue项目下使用乱码问题 当你vue项目中使用h2c模式进行截图时,画布左上角可能会出现一些奇怪的字符,这是由于`noscript`标签导致的,将其删除即可。 ### 参数说明 截图插件有一个可选参数,它接受一个对象,对象每个key的作用如下: * `capture` 推荐使用的新截图配置,值为`Object`类型: * `source` 截图来源,值为`display-media | injected-stream | dom | snapdom | image` * `render` 渲染策略,值为`browser-frame | window-frame` * `cursor` 屏幕捕获流是否包含鼠标指针,值为`never | motion | always`,默认 `never`,仅对 `source: "display-media"` 生效 * `stream``source``injected-stream` 时必传,值为 `MediaStream` * `imageSrc``source``image` 时必传,值为 `string` * `snapdom``source``snapdom` 时可传,值为 SnapDOM 导出的 `snapdom` 对象;不传时会读取 `window.snapdom` * `snapdomOptions``source``snapdom` 时可传,值为 SnapDOM 的截图配置 * `enableWebRtc` 已废弃。旧写法中用于控制是否启用 webrtc,值为`boolean`类型,值为`false`则使用`html2canvas`来截图 * `screenFlow` 已废弃。旧写法中用于传入设备提供的屏幕流数据(常用于electron环境) * `completeCallback` 截图完成回调函数,值为`Function`类型,最右侧的对号图标点击后会将图片的base64地址与裁剪信息回传给你定义的函数,如果不传的话则会将这些数据放到`sessionStorage`中,你可以通过下述方式拿到他: ```javascript sessionStorage.getItem("screenShotImg"); ``` * `closeCallback` 截图关闭回调函数,值为`Function`类型。 * `triggerCallback` 截图响应回调函数,值为`Function`类型,使用html2canvas截屏时,页面图片过多时响应会较慢;使用webrtc截屏时用户点了分享,该函数为响应完成后触发的事件。回调函数返回一个对象,类型为: `{code: number,msg: string, displaySurface: string | null,displayLabel: string | null}`,code为0时代表截图加载完成,displaySurface返回的的是当前选择的窗口类型,displayLabel返回的是当前选择的标签页标识,浏览器不支持时此值为null。 * `cancelCallback` 取消分享回到函数,值为`Function`类型,使用webrtc模式截屏时,用户点了取消或者浏览器不支持时所触发的事件。回调函数返回一个对象,类型为:`{code: number,msg: string, errorInfo: string}`,code为-1时代表用户未授权或者浏览器不支持webrtc。 * `saveCallback` 保存截图回调函数,值为`Function`类型。回调函数中返回三个参数: * `code` 状态码,number类型,为0时代表保存成功 * `msg` 消息码,string类型。 * `base64` 截图的base64信息,string类型。 * `level` 截图容器层级,值为number类型。 * `cutBoxBdColor` 裁剪区域边框像素点颜色,值为string类型。 * `maxUndoNum` 最大可撤销次数, 值为number类型 * `canvasWidth` 画布宽度,值为number类型,必须与高度一起设置,单独设置无效。 * `canvasHeight` 画布高度,值为number类型,必须与宽度一起设置,单独设置无效。 * `position` 截图容器位置,值为`{left?: number, top?: number}`类型 * `clickCutFullScreen` 单击截全屏启用状态,值为`boolean`类型, 默认为`false` * `hiddenToolIco` 需要隐藏的截图工具栏图标,值为`Object`类型,默认为`{}`。传你需要隐藏的图标名称,将值设为`true`即可,除关闭图标外,其他图标均可隐藏。可隐藏的key如下所示: * `square` 矩形绘制 * `round` 圆形绘制 * `rightTop` 箭头绘制 * `brush` 涂鸦 * `mosaicPen`马赛克工具 * `text` 文本工具 * `separateLine` 分割线 * `save` 下载图片 * `undo` 撤销工具 * `confirm` 保存图片 * `showScreenData` 截图组件加载完毕后,是否显示截图内容至canvas画布内,值为`boolean`类型,默认为`false`* `customRightClickEvent` 自定义容器的右键点击事件,值为`Object`类型,接受2个参数: * `state` 是否拦截右键点击,值为boolean类型,默认为`false` * `handleFn` 拦截后的事件处理函数,该属性为可选项,如果不传,默认行为是销毁组件。 * `imgSrc` 已废弃。旧写法中用于传入截图内容(例如`electron`环境下已有图片数据)。“新写法”请改用 `capture.imageSrc` * `loadCrossImg` 是否加载跨域图片,值为`boolean`类型,默认为`false`* `proxyUrl` 代理服务器地址,值为`string`类型,默认为"" * `screenShotDom` 需要进行截图的容器,值为`HTMLElement`类型,默认使用的是`body`* `useRatioArrow` 是否使用等比例箭头, 默认为false(递增变粗的箭头)。 * `imgAutoFit` 是否开启图片自适应, 默认为false。如果自定义了截图内容,浏览器的缩放比例不为100%时,可以设置此参数来修复图片与蒙板大小不一致的问题。 * `cropBoxInfo` 初始裁剪框,值为`{ x: number; y: number; w: number; h: number }`类型,默认不加载。 * `wrcReplyTime` webrtc模式捕捉屏幕时的响应时间,值为`number`类型,默认为500ms。 * `wrcImgPosition` webrtc模式下是否需要对图像进行裁剪,值为`{ x: number; y: number; w: number; h: number }`类型,默认为不裁剪。 * `noScroll` 是否禁止页面滚动,值为`boolean`类型,默认为`false`* `maskColor` 蒙层颜色,值为`{ r: number; g: number; b: number; a: number }`类型,默认为:`{ r: 0; g: 0; b: 0; a: 0.6 }` * `toolPosition` 工具栏展示位置,值为`string`类型,默认为居中展示,提供三个选项: * `left` 左对齐于裁剪框 * `center` 居中对齐于裁剪框 * `right` 右对齐于裁剪框 * `writeBase64` 是否将截图内容写入剪切板,值为`boolean`类型,默认为`true` * `exportOptions` 截图导出配置,值为`Object`类型: * `type` 导出图片类型,值为`image/png | image/jpeg | image/webp`,默认为`image/png` * `quality` 导出图片质量,值为`0 ~ 1`之间的数字,默认为`0.75`。该参数主要对`image/jpeg``image/webp`生效,`image/png`通常会忽略质量参数。 * `wrcWindowMode` 已废弃。旧写法中用于启用窗口截图模式。新写法请改用 `capture.render = "window-frame"` * `hiddenScrollBar` 是否隐藏滚动条,用webrtc模式截图时chrome 112版本的浏览器在部分系统下会挤压出现滚动条,如果出现你可以尝试通过此参数来进行修复。值为`Object`类型,有4个属性: * `state: boolean`; 启用状态, 默认为`false` * `fillState?: boolean`; 填充状态,默认为`false` * `color?: string`; 填充层颜色,滚动条隐藏后可能会出现空缺,需要进行填充,默认填充色为黑色。 * `fillWidth?: number`; 填充层宽度,默认为截图容器的宽度 * `fillHeight?: number`; 填充层高度,默认为空缺区域的高度 > 使用当前标签页进行截图相对而言用户体验是最好的,但是因为`chrome 112`版本的bug会造成页面内容挤压导致截取到的内容不完整,因此只能采用其他方案来解决此问题了。`wrcWindowMode`和`hiddenScrollBar`都可以解决这个问题。 > * `wrcWindowMode`方案会更完美些,但是用户授权时会出现其他的应用程序选项,用户体验会差一些 > * `hiddenScrollBar`方案还是采用标签页截图,但是会造成内容挤压,底部出现空白。 > > 两种方案的优点与缺点讲完了,最好的办法还是希望`chrome`能在之后的版本更新中修复此问题。 ### 迁移建议 如果你正在从旧参数迁移到新参数,可以按下面的映射来替换: * `enableWebRtc: true` -> `capture.source: "display-media"` * `enableWebRtc: false` -> `capture.source: "dom"` * `screenFlow` -> `capture.source: "injected-stream" + capture.stream` * `imgSrc` -> `capture.source: "image" + capture.imageSrc` * `wrcWindowMode: true` -> `capture.render: "window-frame"` * `wrcWindowMode: false` -> `capture.render: "browser-frame"` > 当前版本仍兼容旧参数,但运行时会给出废弃提示。建议尽快切换到 `capture`,旧参数将在不久的将来移除。 > 上述类型中的`?:`为ts中的可选类型,意思为:这个key是可选的,如果需要就传,不需要就不传。 > imgSrc是url时,如果图片资源跨域了,必须让图片服务器允许跨域才能正常加载。同样的loadCrossImg设置为true时,图片资源跨域了也需要让图片服务器允许跨域。 ### 快捷键监听 插件容器监听了三个快捷键,如下所示: * `Esc`,按下键盘上的esc键时,等同于点了工具栏的关闭图标。 * `Enter`,按下键盘上的enter键时,等同于点了截图工具栏的确认图标。 * `Ctrl/Command + z`,按下这两个组合键时,等同于点了截图工具栏的撤销图标。 ### 额外提供的API 插件暴露了一些内部变量出来,便于调用者根据自己的需求进行修改。 #### getCanvasController 该函数用于获取截图容器的DOM,返回值为`HTMLCanvasElement`类型。 示例代码: ```javascript import ScreenShot from "js-web-screen-shot"; const screenShotHandler = new ScreenShot(); const canvasDom = screenShotHandler.getCanvasController(); ``` > 注意:如果截图容器尚未加载完毕,获取到的内容可能为null。 #### destroyComponents 该函数用于销毁截图容器,无返回值。 示例代码: ```javascript import ScreenShot from "js-web-screen-shot"; const screenShotHandler = new ScreenShot(); screenShotHandler.destroyComponents() ``` #### completeScreenshot 该函数用于将框选区域的截图内容写入剪切版,无返回值。 该方法可以跟`cropBoxInfo`参数结合起来实现指定位置的自动截图,截图内容默认写入剪切版内,如果你想拿到截取到的base64内容可以通过`completeCallback`参数拿到,或者直接从sessionStorage中获取。 该回调函数中返回的参数格式如下所示: * base64 * cutInfo 裁剪框位置参数 * startX * startY * width * height 示例代码: ```javascript const plugin = new screenShotPlugin( { clickCutFullScreen:true, capture: { source: "display-media", render: "window-frame" }, cropBoxInfo:{x:350, y:20, w:300, h:300}, completeCallback: ({base64, cutInfo}) => { console.log(base64, cutInfo); }, triggerCallback:() => { // 截图组件加载完毕调用此方法来完成框选区域的截图 plugin.completeScreenshot() } }); ``` > 注意:此方法在1.9.9版本之后不再返回字符串类型的数据,而是返回的对象格式。 #### getCutBoxInfo 该函数用于获取当前裁剪框的位置信息,返回值为一个对象: * `startX` x 点坐标 * `startY` y 点坐标 * `width` 裁剪框宽度 * `height` 裁剪框高度 示例代码: ```javascript import ScreenShot from "js-web-screen-shot"; const screenShotHandler = new ScreenShot(); const info = screenShotHandler.getCutBoxInfo(); ``` ### 工具栏图标定制 如果你需要修改截图工具栏的图标,可以通过覆盖元素css类名的方式实现,插件内所有图标的css类名如下所示: * square 矩形绘制图标 * round 圆型绘制图标 * right-top 箭头绘制图标 * brush 画笔工具 * mosaicPen 马赛克工具 * text 文本工具 * save 保存 * close 关闭 * undo 撤销 * confirm 确认 以`square`为例,要修改它的图标,只需要将下述代码添加进你项目代码的样式中即可。 ```scss .square { background-image: url("你的图标路径") !important; &:hover { background-image: url("你的图标路径") !important; } &:active { background-image: url("你的图标路径") !important; } } ``` ## 写在最后 至此,插件的所有使用方法就介绍完了,该插件的Vue3版本,请移步:[vue-web-screen-shot](https://www.npmjs.com/package/vue-web-screen-shot)