ybg-screen-shot
Version:
web端自定义截屏插件(原生JS版)
671 lines (644 loc) • 22.1 kB
text/typescript
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";
import html2canvas from "html2canvas";
import PlugInParameters from "@/lib/main-entrance/PlugInParameters";
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 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(options: {
enableWebRtc: boolean;
level: number;
completeCallback: Function;
}) {
const plugInParameters = new PlugInParameters();
// webrtc启用状态
if (
options &&
Object.prototype.hasOwnProperty.call(options, "enableWebRtc")
) {
plugInParameters.setWebRtcStatus(options.enableWebRtc);
}
// 设置回调函数
if (
options &&
Object.prototype.hasOwnProperty.call(options, "completeCallback")
) {
// 创建dom
new CreateDom(options.completeCallback);
} else {
// 创建dom
new CreateDom((base64: string) => {
sessionStorage.setItem("screenShotImg", base64);
});
}
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();
const screenShotContainer = document.getElementById("screenShotContainer");
if (screenShotContainer == null) return;
// 调整层级
screenShotContainer.style.zIndex = options?.level + "";
}
// 加载截图组件
private load() {
const plugInParameters = new PlugInParameters();
// 设置截图区域canvas宽高
this.data.setScreenShortInfo(window.innerWidth, window.innerHeight);
// 设置截图图片存放容器宽高
this.screenShortImageController.width = window.innerWidth;
this.screenShortImageController.height = window.innerHeight;
// 获取截图区域画canvas容器画布
const context = this.screenShortController?.getContext("2d");
if (context == null) return;
// 启用webrtc截屏时则修改容器宽高
if (plugInParameters.getWebRtcStatus()) {
// 设置为屏幕宽高
this.data.setScreenShortInfo(window.screen.width, window.screen.height);
// 设置为屏幕宽高
this.screenShortImageController.width = window.screen.width;
this.screenShortImageController.height = window.screen.height;
}
// 显示截图区域容器
this.data.showScreenShortPanel();
if (!plugInParameters.getWebRtcStatus()) {
// html2canvas截屏
html2canvas(document.body, {}).then(canvas => {
// 装载截图的dom为null则退出
if (this.screenShortController == null) return;
// 存放html2canvas截取的内容
this.screenShortImageController = canvas;
// 赋值截图区域canvas画布
this.screenShortCanvas = context;
// 绘制蒙层
drawMasking(context);
// 添加监听
this.screenShortController?.addEventListener(
"mousedown",
this.mouseDownEvent
);
this.screenShortController?.addEventListener(
"mousemove",
this.mouseMoveEvent
);
this.screenShortController?.addEventListener(
"mouseup",
this.mouseUpEvent
);
});
return;
}
// 截取整个屏幕
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) {
// 销毁截图组件
this.data.destroyDOM();
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();
}, 500);
});
};
// 鼠标按下事件
private mouseDownEvent = (event: MouseEvent) => {
// 当前操作的是撤销
if (this.data.getToolName() == "undo") return;
this.data.setDragging(true);
this.clickFlag = true;
const mouseX = nonNegativeData(event.offsetX);
const mouseY = nonNegativeData(event.offsetY);
// 如果当前操作的是截图工具栏
if (this.data.getToolClickStatus()) {
// 记录当前鼠标开始坐标
this.drawGraphPosition.startX = mouseX;
this.drawGraphPosition.startY = mouseY;
} else {
// 隐藏截图工具栏
this.data.setToolStatus(false);
}
// 当前操作的是画笔
if (this.data.getToolName() == "brush" && this.screenShortCanvas) {
// 初始化画笔
initPencil(this.screenShortCanvas, mouseX, mouseY);
}
// 当前操作的文本
if (
this.data.getToolName() == "text" &&
this.textInputController &&
this.screenShortController &&
this.screenShortCanvas
) {
// 修改鼠标样式
this.screenShortController.style.cursor = "text";
// 显示文本输入区域
this.data.setTextStatus(true);
// 判断输入框位置是否变化
if (
this.textInputPosition.mouseX != 0 &&
this.textInputPosition.mouseY != 0 &&
this.textInputPosition.mouseX != mouseX &&
this.textInputPosition.mouseY != mouseY
) {
drawText(
this.textInputController.innerText,
this.textInputPosition.mouseX,
this.textInputPosition.mouseY,
this.data.getSelectedColor(),
this.fontSize,
this.screenShortCanvas
);
// 清空文本输入区域的内容
this.textInputController.innerHTML = "";
// 保存绘制记录
this.addHistory();
}
// 计算文本框显示位置
const textMouseX = mouseX - 15;
const textMouseY = mouseY - 15;
// 修改文本区域位置
this.textInputController.style.left = textMouseX + "px";
this.textInputController.style.top = textMouseY + "px";
setTimeout(() => {
// 获取焦点
if (this.textInputController) {
this.textInputController.focus();
// 记录当前输入框位置
this.textInputPosition = { mouseX: mouseX, mouseY: mouseY };
}
});
}
// 如果操作的是裁剪框
if (this.borderOption) {
// 设置为拖动状态
this.data.setDraggingTrim(true);
// 记录移动时的起始点坐标
this.movePosition.moveStartX = mouseX;
this.movePosition.moveStartY = mouseY;
} else {
// 绘制裁剪框,记录当前鼠标开始坐标
this.drawGraphPosition.startX = mouseX;
this.drawGraphPosition.startY = mouseY;
}
};
// 鼠标移动事件
private mouseMoveEvent = (event: MouseEvent) => {
if (
this.screenShortCanvas == null ||
this.screenShortController == null ||
this.data.getToolName() == "undo"
)
return;
this.clickFlag = false;
// 获取裁剪框位置信息
const { startX, startY, width, height } = this.drawGraphPosition;
// 获取当前鼠标坐标
const currentX = nonNegativeData(event.offsetX);
const currentY = nonNegativeData(event.offsetY);
// 裁剪框临时宽高
const tempWidth = currentX - startX;
const tempHeight = currentY - startY;
// 工具栏绘制
if (this.data.getToolClickStatus() && this.data.getDragging()) {
// 当前操作的不是马赛克则显示最后一次画布绘制时的状态
if (this.data.getToolName() != "mosaicPen") {
this.showLastHistory();
}
switch (this.data.getToolName()) {
case "square":
drawRectangle(
startX,
startY,
tempWidth,
tempHeight,
this.data.getSelectedColor(),
this.data.getPenSize(),
this.screenShortCanvas,
this.screenShortController,
this.screenShortImageController
);
break;
case "round":
drawCircle(
this.screenShortCanvas,
currentX,
currentY,
startX,
startY,
this.data.getPenSize(),
this.data.getSelectedColor()
);
break;
case "right-top":
drawLineArrow(
this.screenShortCanvas,
startX,
startY,
currentX,
currentY,
30,
10,
this.data.getPenSize(),
this.data.getSelectedColor()
);
break;
case "brush":
// 画笔绘制
drawPencil(
this.screenShortCanvas,
currentX,
currentY,
this.data.getPenSize(),
this.data.getSelectedColor()
);
break;
case "mosaicPen":
// 绘制马赛克,为了确保鼠标位置在绘制区域中间,所以对x、y坐标进行-10处理
drawMosaic(
currentX - 10,
currentY - 10,
this.data.getPenSize(),
this.degreeOfBlur,
this.screenShortCanvas
);
break;
default:
break;
}
return;
}
// 执行裁剪框操作函数
this.operatingCutOutBox(
currentX,
currentY,
startX,
startY,
width,
height,
this.screenShortCanvas
);
// 如果鼠标未点击或者当前操作的是裁剪框都return
if (!this.data.getDragging() || this.data.getDraggingTrim()) return;
// 绘制裁剪框
this.tempGraphPosition = drawCutOutBox(
startX,
startY,
tempWidth,
tempHeight,
this.screenShortCanvas,
this.data.getBorderSize(),
this.screenShortController,
this.screenShortImageController
) as drawCutOutBoxReturnType;
};
// 鼠标抬起事件
private mouseUpEvent = () => {
// 当前操作的是撤销
if (this.data.getToolName() == "undo") return;
// 绘制结束
this.data.setDragging(false);
this.data.setDraggingTrim(false);
if (this.screenShortController == null || this.screenShortCanvas == null) {
return;
}
if (this.data.getToolClickStatus()) {
// 保存绘制记录
this.addHistory();
return;
}
// 保存绘制后的图形位置信息
this.drawGraphPosition = this.tempGraphPosition;
// 如果工具栏未点击则保存裁剪框位置
if (!this.data.getToolClickStatus()) {
const { startX, startY, width, height } = this.drawGraphPosition;
this.data.setCutOutBoxPosition(startX, startY, width, height);
}
// 保存边框节点信息
this.cutOutBoxBorderArr = saveBorderArrInfo(
this.data.getBorderSize(),
this.drawGraphPosition
);
if (this.screenShortController != null) {
// 修改鼠标状态为拖动
this.screenShortController.style.cursor = "move";
// 显示截图工具栏
this.data.setToolStatus(true);
if (this.toolController != null) {
// 计算截图工具栏位置
const toolLocation = calculateToolLocation(
this.drawGraphPosition,
this.toolController.offsetWidth
);
// 显示并设置截图工具栏位置
this.data.setToolInfo(toolLocation.mouseX, toolLocation.mouseY);
}
}
};
/**
* 操作裁剪框
* @param currentX 裁剪框当前x轴坐标
* @param currentY 裁剪框当前y轴坐标
* @param startX 鼠标x轴坐标
* @param startY 鼠标y轴坐标
* @param width 裁剪框宽度
* @param height 裁剪框高度
* @param context 需要进行绘制的canvas画布
* @private
*/
private operatingCutOutBox(
currentX: number,
currentY: number,
startX: number,
startY: number,
width: number,
height: number,
context: CanvasRenderingContext2D
) {
// canvas元素不存在
if (this.screenShortController == null) {
return;
}
// 获取鼠标按下时的坐标
const { moveStartX, moveStartY } = this.movePosition;
// 裁剪框边框节点事件存在且裁剪框未进行操作,则对鼠标样式进行修改
if (this.cutOutBoxBorderArr.length > 0 && !this.data.getDraggingTrim()) {
// 标识鼠标是否在裁剪框内
let flag = false;
// 判断鼠标位置
context.beginPath();
for (let i = 0; i < this.cutOutBoxBorderArr.length; i++) {
context.rect(
this.cutOutBoxBorderArr[i].x,
this.cutOutBoxBorderArr[i].y,
this.cutOutBoxBorderArr[i].width,
this.cutOutBoxBorderArr[i].height
);
if (context.isPointInPath(currentX, currentY)) {
switch (this.cutOutBoxBorderArr[i].index) {
case 1:
if (this.data.getToolClickStatus()) {
this.screenShortController.style.cursor = "crosshair";
} else {
this.screenShortController.style.cursor = "move";
}
break;
case 2:
this.screenShortController.style.cursor = "ns-resize";
break;
case 3:
this.screenShortController.style.cursor = "ew-resize";
break;
case 4:
this.screenShortController.style.cursor = "nwse-resize";
break;
case 5:
this.screenShortController.style.cursor = "nesw-resize";
break;
default:
break;
}
this.borderOption = this.cutOutBoxBorderArr[i].option;
flag = true;
break;
}
}
context.closePath();
if (!flag) {
// 鼠标移出裁剪框重置鼠标样式
this.screenShortController.style.cursor = "default";
// 重置当前操作的边框节点为null
this.borderOption = null;
}
}
// 裁剪框正在被操作
if (this.data.getDraggingTrim()) {
// 当前操作节点为1时则为移动裁剪框
if (this.borderOption === 1) {
// 计算要移动的x轴坐标
const x = fixedData(
currentX - (moveStartX - startX),
width,
this.screenShortController.width
);
// 计算要移动的y轴坐标
const y = fixedData(
currentY - (moveStartY - startY),
height,
this.screenShortController.height
);
// 重新绘制裁剪框
this.tempGraphPosition = drawCutOutBox(
x,
y,
width,
height,
context,
this.data.getBorderSize(),
this.screenShortController as HTMLCanvasElement,
this.screenShortImageController
) as drawCutOutBoxReturnType;
} else {
// 裁剪框其他8个点的拖拽事件
const {
tempStartX,
tempStartY,
tempWidth,
tempHeight
} = zoomCutOutBoxPosition(
currentX,
currentY,
startX,
startY,
width,
height,
this.borderOption as number
) as zoomCutOutBoxReturnType;
// 绘制裁剪框
this.tempGraphPosition = drawCutOutBox(
tempStartX,
tempStartY,
tempWidth,
tempHeight,
context,
this.data.getBorderSize(),
this.screenShortController as HTMLCanvasElement,
this.screenShortImageController
) as drawCutOutBoxReturnType;
}
}
}
/**
* 显示最新的画布状态
* @private
*/
private showLastHistory() {
if (this.screenShortCanvas != null) {
const context = this.screenShortCanvas;
if (this.data.getHistory().length <= 0) {
this.addHistory();
}
context.putImageData(
this.data.getHistory()[this.data.getHistory().length - 1]["data"],
0,
0
);
}
}
/**
* 保存当前画布状态
* @private
*/
private addHistory() {
if (this.screenShortCanvas != null && this.screenShortController != null) {
// 获取canvas画布与容器
const context = this.screenShortCanvas;
const controller = this.screenShortController;
if (this.data.getHistory().length > this.maxUndoNum) {
// 删除最早的一条画布记录
this.data.shiftHistory();
}
// 保存当前画布状态
this.data.pushHistory({
data: context.getImageData(0, 0, controller.width, controller.height)
});
// 启用撤销按钮
this.data.setUndoStatus(true);
}
}
}