前幾天我發佈了一個web端自定義截圖的插件,在使用過程當中有開發者反饋這個插件沒法在vue2項目中使用,因而,我就開始找問題,發現個人插件是基於Vue3的開發的,因爲Vue3的插件和Vue2的插件徹底不兼容,所以插件也就只能在Vue3項目中使用。javascript
通過一番考慮後,我決定用原生js來重構這個插件,讓其不依賴任何庫,這樣它就能運行在任意一臺支持js的設備上,本文就跟你們分享下我重構這個插件的過程,歡迎各位感興趣的開發者閱讀本文。css
運行結果視頻:實現web端自定義截屏html
本文不講解插件的具體實現思路,對插件實現思路感興趣的開發者請移步:實現Web端自定義截屏vue
我想使用ts、scss、eslint、prettier來提高插件的可維護性,又嫌麻煩,不想手動配置webpack環境,因而我決定使用Vue CLI來搭建插件開發環境。java
本文不細講Vue CLI搭建插件開發環境的過程,對此感興趣的開發者請移步:使用CLI開發一個Vue3的npm庫。webpack
咱們搭建好插件的開發環境後,CLI默認會在package.json中添加Vue的相關包,咱們的插件不會依賴於vue,所以咱們把它刪除便可。git
{
- "vue": "^3.0.0-0",
- "vue-class-component": "^8.0.0-0"
}
複製代碼
爲了方便開發者使用dom,這裏選擇使用js動態來建立dom,最後將其掛載到body中,在vue3版本的截圖插件中,咱們可使用vue組件來輔助咱們,這裏咱們就要基於組件來使用js來建立對應的dom,爲其綁定對應的事件。github
部分實現代碼以下,完整代碼請移步:CreateDom.tsweb
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方法,個人預想效果是:用戶在使用我插件時,直接實例化插件就能正常運行。typescript
所以,咱們默認暴露出一個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();
}
/** 其餘代碼省略 **/
}
複製代碼
作完上述配置後咱們的插件開發環境就搭建好了,我執行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
插件一開始使用的是html2canvas來將dom轉換爲canvas的,由於他要遍歷整個body中的dom,而後再轉換成canvas,並且圖片還不能跨域,若是頁面中圖片一多,它會變得很是慢。
在上一篇文章的評論區中有位開發者 @名字什麼的都不重要 建議我使用webrtc來替代html2canvas,因而我就看了下webrtc的相關文檔,最終實現了截屏功能,它截取出來的東西更精確、性能更好,不存在卡頓問題也不存在css問題,並且它把選擇權交給了用戶,讓用戶決定來共享屏幕的那一部份內容。
接下來就跟你們分享下個人實現思路:
getDisplayMedia
來捕獲屏幕,獲得MediaStream
流MediaStream
流輸出到video標籤中有關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