在Http开发中,svg标签电子画板功能包含正方形、文本、橡皮 (颜色、尺寸、不透明度)、 撤销、取消撤销 等等功能,
效果图

代码如下:
<!DOCTYPE html>
<html lang="en">
<!--
<link href="/Index/css/materialdesignicons.min.css" rel="stylesheet">
-->
<head>
<meta charset="utf-8"/>
<title>电子白板</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0"/>
<!-- <meta name="description" content="{{translations.tagline}}" />
<meta name="keywords" content="{{translations.collaborative_whiteboard}},online,draw,paint,shared,realtime,wbo,whitebophir" />
<meta property="og:title" content="{{board}} board on WBO" />
<meta property="og:url" content="{{baseUrl}}/boards/{{boardUriComponent}}" />
<meta property="og:image" content="{{baseUrl}}/preview/{{boardUriComponent}}" />-->
<!-- <link rel="apple-touch-icon" href="/WhiteBoard/img/favicon.svg">-->
<link rel="stylesheet" type="text/css" href="/WhiteBoard/css/board.css"/>
<!-- <link rel="canonical" href="{{boardUriComponent}}?lang={{language}}" />
{{#languages}}
<link rel="alternate" hreflang="{{.}}" href="{{../boardUriComponent}}?lang={{.}}" />
{{/languages}}-->
</head>
<style>
body, html {
margin: 0;
padding: 0;
height: 100%;
}
svg {
width: 100%;
height: 100%;
}
#textInputContainer {
position: absolute;
display: none;
background: white;
padding: 5px;
border: 1px solid #ccc;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
#textInput {
width: 200px;
padding: 5px;
}
</style>
<body>
<div id="board" style="width:100%; height:100%;">
<svg id="drawing-board" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs id="defs"></defs>
<g id="drawingArea"></g>
<g id="cursors"></g>
</svg>
</div>
<div id="loadingMessage" class="hidden">载入中</div>
<div id="menu" tabindex="0">
<div id="menuItems">
<ul id="tools" class="tools">
<li class="tool hasSecondary curTool" tabindex="-1" id="pencil" title="铅笔" onclick="toolFun('pencil')">
<img class="tool-icon primaryIcon" width="35" height="35" src="/WhiteBoard/tools/pencil/icon.svg"
alt="/WhiteBoard/tools/pencil/icon.svg"/>
<span class="tool-name">铅笔</span>
<img class="tool-icon secondaryIcon" width="35" height="35"
src="/WhiteBoard/tools/pencil/whiteout_tape.svg" alt="icon"/>
</li>
<li class="tool hasSecondary" tabindex="-1" id="line" title="直线 " onclick="toolFun('line')" >
<img class="tool-icon" width="35" height="35" src="/WhiteBoard/tools/line/icon.svg"
alt="/WhiteBoard/tools/pencil/icon.svg"/>
<span class="tool-name">直线</span>
<img class="tool-icon secondaryIcon" width="35" height="35"
src="/WhiteBoard/tools/line/icon-straight.svg" alt="icon"/>
</li>
<li class="tool hasSecondary" tabindex="-1" id="rect" title="矩形" onclick="toolFun('rect')" >
<img class="tool-icon primaryIcon" width="35" height="35" src="/WhiteBoard/tools/rect/icon.svg"
alt="/WhiteBoard/tools/rect/icon.svg">
<span class="tool-name">矩形</span>
<img class="tool-icon secondaryIcon" width="35" height="35" src="/WhiteBoard/tools/rect/icon-square.svg"
alt="icon">
</li>
<li class="tool hasSecondary" tabindex="-1" id="ellipse" title="椭圆" onclick="toolFun('ellipse')">
<img class="tool-icon primaryIcon" width="35" height="35"
src="/WhiteBoard/tools/ellipse/icon-ellipse.svg" alt="/WhiteBoard/tools/ellipse/icon-ellipse.svg">
<span class="tool-name">椭圆</span>
<img class="tool-icon secondaryIcon" width="35" height="35"
src="/WhiteBoard/tools/ellipse/icon-circle.svg" alt="icon">
</li>
<li class="tool" tabindex="-1" id="text" title="文本" onclick="toolFun('text')">
<img class="tool-icon" width="35" height="35" src="/WhiteBoard/tools/text/icon.svg"
alt="/WhiteBoard/tools/text/icon.svg">
<span class="tool-name">文本</span>
<img class="tool-icon secondaryIcon" width="35" height="35" src="data:," alt="icon">
</li>
<li class="tool" tabindex="-1" id="eraser" title="橡皮 " onclick="toolFun('eraser')" >
<img class="tool-icon" width="35" height="35" src="/WhiteBoard/tools/eraser/icon.svg"
alt="/WhiteBoard/tools/eraser/icon.svg">
<span class="tool-name">橡皮</span>
<img class="tool-icon secondaryIcon" width="35" height="35" src="data:," alt="icon">
</li>
<!-- <li class="tool hasSecondary curTool" tabindex="-1" id="toolID-Hand" title="移动 (键盘快捷键: h) [点击以切换]">
<img class="tool-icon primaryIcon" width="35" height="35" src="/WhiteBoard/tools/hand/hand.svg"
alt="/WhiteBoard/tools/hand/hand.svg">
<span class="tool-name">移动</span>
<img class="tool-icon secondaryIcon" width="35" height="35" src="/WhiteBoard/tools/hand/selector.svg"
alt="icon">
</li>
<li class="tool oneTouch" tabindex="-1" id="toolID-Grid" title="网格 (键盘快捷键: g)">
<img class="tool-icon" width="35" height="35" src="/WhiteBoard/tools/grid/icon.svg"
alt="/WhiteBoard/tools/grid/icon.svg">
<span class="tool-name">网格</span>
<img class="tool-icon secondaryIcon" width="35" height="35" src="data:," alt="icon">
</li>
<li class="tool oneTouch" tabindex="-1" id="toolID-Download" title="下载 (键盘快捷键: d)">
<img class="tool-icon" width="35" height="35" src="/WhiteBoard/tools/download/download.svg"
alt="/WhiteBoard/tools/download/download.svg">
<span class="tool-name">下载</span>
<img class="tool-icon secondaryIcon" width="35" height="35" src="data:," alt="icon">
</li>
<li class="tool" tabindex="-1" id="toolID-Zoom" title="放大 (键盘快捷键: z)">
<img class="tool-icon" width="35" height="35" src="/WhiteBoard/tools/zoom/icon.svg"
alt="/WhiteBoard/tools/zoom/icon.svg">
<span class="tool-name">放大</span>
<img class="tool-icon secondaryIcon" width="35" height="35" src="data:," alt="icon">
</li>-->
</ul>
<ul class="tools" id="settings">
<li class="tool" tabindex="-1">
<input class="tool-icon" type="color" id="chooseColor" value="#001f3f">
<label class="tool-name" for="chooseColor">颜色</label>
<span class="colorPresets" id="colorPreset" title="001f3f" >
<span class="colorPresetButton" id="color_001f3f" title="#001f3f" style="background-color: rgb(0, 31, 63);"></span>
<span class="colorPresetButton" id="color_FF4136" title="#FF4136" style="background-color: rgb(255, 65, 54);"></span>
<span class="colorPresetButton" id="color_0074D9" title="#0074D9" style="background-color: rgb(0, 116, 217);"></span>
<span class="colorPresetButton" id="color_FF851B" title="#FF851B" style="background-color: rgb(255, 133, 27);"></span>
<span class="colorPresetButton" id="color_FFDC00" title="#FFDC00" style="background-color: rgb(255, 220, 0);"></span>
<span class="colorPresetButton" id="color_3D9970" title="#3D9970" style="background-color: rgb(61, 153, 112);"></span>
<span class="colorPresetButton" id="color_91E99B" title="#91E99B" style="background-color: rgb(145, 233, 155);"></span>
<span class="colorPresetButton" id="color_90468b" title="#90468b" style="background-color: rgb(144, 70, 139);"></span>
<span class="colorPresetButton" id="color_7FDBFF" title="#7FDBFF" style="background-color: rgb(127, 219, 255);"></span>
<span class="colorPresetButton" id="color_AAAAAA" title="#AAAAAA" style="background-color: rgb(170, 170, 170);"></span>
<span class="colorPresetButton" id="color_E65194" title="#E65194" style="background-color: rgb(230, 81, 148);"></span>
</span>
</li>
<li class="tool" tabindex="-1" title="尺寸 ">
<img class="tool-icon" width="60" height="60" src="/WhiteBoard/img/icon-size.svg" alt="size">
<label class="tool-name slider" for="size">
<span>尺寸</span>
<input type="range" id="size" value="4" min="1" max="50" step="1" class="rangeChooser">
</label>
</li>
<li class="tool" tabindex="-1">
<span class="tool-icon">
<svg viewBox="0 0 8 8">
<pattern id="opacityPattern" x="0" y="0" width="4" height="4" patternUnits="userSpaceOnUse">
<rect x="0" y="0" width="2" height="2" fill="black"></rect>
<rect x="2" y="2" width="2" height="2" fill="black"></rect>
<rect x="2" y="0" width="2" height="2" fill="#eeeeee"></rect>
<rect x="0" y="2" width="2" height="2" fill="#eeeeee"></rect>
</pattern>
<circle cx="4" cy="4" id="opacityIndicator" r="3.5" fill="url(#opacityPattern)" opacity="1"></circle>
</svg>
</span>
<label class="tool-name slider" for="chooseOpacity">
<span>不透明度</span>
<input type="range" id="chooseOpacity" value="1" min="0.2" max="1" step="0.1" class="rangeChooser">
</label>
</li>
</li>
<li class="tool hasSecondary" tabindex="-1" id="undo" title="上一步" >
<img class="tool-icon primaryIcon" width="35" height="35" src="/WhiteBoard/tools/img/undo.svg" >
<span class="tool-name">上一步</span>
</li>
<li class="tool hasSecondary" tabindex="-1" id="redo" title="下一步" >
<img class="tool-icon primaryIcon" width="35" height="35" src="/WhiteBoard/tools/img/redo.svg" >
<span class="tool-name">下一步</span>
</li>
</ul>
</div>
</div>
<div id="textInputContainer">
<input type="text" id="textInput" placeholder="输入文本">
<button id="textConfirm">确定</button>
<button id="textCancel">取消</button>
</div>
<script type="text/javascript" src="/js/jquery-3.6.0.js"></script>
<script type="text/javascript" src="/js/axios.js"></script>
<script>
const tools_lis = document.getElementById('tools').getElementsByTagName("li");
const drawingBoard = document.getElementById('drawing-board');
const pencilBtn = document.getElementById('pencil');
const lineBtn = document.getElementById('line');
const ellipseBtn = document.getElementById('ellipse');
const rectBtn = document.getElementById('rect');
const textBtn = document.getElementById('text');
const eraserBtn = document.getElementById('eraser');
const colorInput = document.getElementById('colorPreset');
const sizeInput = document.getElementById('size');
const opacityInput = document.getElementById('chooseOpacity');
const undoBtn = document.getElementById('undo');
const redoBtn = document.getElementById('redo');
//text
const textInputContainer = document.getElementById('textInputContainer');
const textInput = document.getElementById('textInput');
const textConfirm = document.getElementById('textConfirm');
const textCancel = document.getElementById('textCancel');
let currentTool = 'pencil';
let isDrawing = false;
let startX, startY;
let path;
let lines = [];
let undoStack = [];
let redoStack = [];
//工具选择
function toolFun(e) {
for (let j = 0; j < tools_lis.length; j++) {
tools_lis[j].classList.remove('curTool');
}
currentTool = e;
document.getElementById(e).classList.add('curTool');
cancelText();
}
//颜色选择
var colorSpans = colorInput.getElementsByTagName("span");
for (let i = 0; i < colorSpans.length; i++) {
colorSpans[i].addEventListener('click', function () {
$("#chooseColor").val(this.title);
});
}
// 文本输入相关
textConfirm.addEventListener('click', confirmText);
textCancel.addEventListener('click', cancelText);
drawingBoard.addEventListener('click', handleCanvasClick);
drawingBoard.addEventListener('mousedown', (e) => {
isDrawing = true;
startX = e.offsetX;
startY = e.offsetY;
if (currentTool === 'pencil') {
path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', `M${startX} ${startY}`);
path.setAttribute('stroke', $("#chooseColor").val());
path.setAttribute('stroke-width', sizeInput.value);
path.setAttribute('fill', 'none');
path.setAttribute('opacity', opacityInput.title);
drawingBoard.appendChild(path);
lines.push(path);
} else if (currentTool === 'line') {
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', startX);
line.setAttribute('y1', startY);
line.setAttribute('x2', startX);
line.setAttribute('y2', startY);
line.setAttribute('stroke', $("#chooseColor").val());
line.setAttribute('stroke-width', sizeInput.value);
line.setAttribute('opacity', opacityInput.value);
drawingBoard.appendChild(line);
lines.push(line);
} else if (currentTool === 'ellipse') {
const ellipse = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse');
ellipse.setAttribute('cx', startX);
ellipse.setAttribute('cy', startY);
ellipse.setAttribute('rx', 0);
ellipse.setAttribute('ry', 0);
ellipse.setAttribute('fill', 'none');
ellipse.setAttribute('stroke-width', sizeInput.value);
ellipse.setAttribute('stroke', $("#chooseColor").val());
ellipse.setAttribute('opacity', opacityInput.value);
drawingBoard.appendChild(ellipse);
lines.push(ellipse);
} else if (currentTool === 'rect') {
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.setAttribute('x', startX);
rect.setAttribute('y', startY);
rect.setAttribute('width', 0);
rect.setAttribute('height', 0);
rect.setAttribute('fill', 'none');
rect.setAttribute('stroke-width', sizeInput.value);
rect.setAttribute('stroke', $("#chooseColor").val());
rect.setAttribute('opacity', opacityInput.value);
drawingBoard.appendChild(rect);
lines.push(rect);
}
else if (currentTool === 'eraser') {
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.setAttribute('x', startX);
rect.setAttribute('y', startY);
rect.setAttribute('width', 0);
rect.setAttribute('height', 0);
rect.setAttribute('fill', 'none');
rect.setAttribute('stroke-width', 2);
rect.setAttribute('stroke', "#000000");
rect.setAttribute('stroke-dasharray', "10 5");
drawingBoard.appendChild(rect);
lines.push(rect);
}
});
drawingBoard.addEventListener('mousemove', (e) => {
if (isDrawing) {
if (currentTool === 'pencil') {
const d = path.getAttribute('d');
path.setAttribute('d', `${d} L${e.offsetX} ${e.offsetY}`);
} else if (currentTool === 'line') {
const lastLine = lines[lines.length - 1];
lastLine.setAttribute('x2', e.offsetX);
lastLine.setAttribute('y2', e.offsetY);
} else if (currentTool === 'ellipse') {
const lastEllipse = lines[lines.length - 1];
const dx = e.offsetX - startX;
const dy = e.offsetY - startY;
lastEllipse.setAttribute('rx', Math.abs(dx));
lastEllipse.setAttribute('ry', Math.abs(dy));
} else if (currentTool === 'rect') {
const lastRect = lines[lines.length - 1];
const dx = e.offsetX - startX;
const dy = e.offsetY - startY;
lastRect.setAttribute('width', Math.abs(dx));
lastRect.setAttribute('height', Math.abs(dy));
if (dx < 0) {
lastRect.setAttribute('x', e.offsetX);
}
if (dy < 0) {
lastRect.setAttribute('y', e.offsetY);
}
}
else if (currentTool === 'eraser'){
const lastRect = lines[lines.length - 1];
const dx = e.offsetX - startX;
const dy = e.offsetY - startY;
lastRect.setAttribute('width', Math.abs(dx));
lastRect.setAttribute('height', Math.abs(dy));
if (dx < 0) {
lastRect.setAttribute('x', e.offsetX);
}
if (dy < 0) {
lastRect.setAttribute('y', e.offsetY);
}
}
}
});
drawingBoard.addEventListener('mouseup', () => {
if (currentTool === 'eraser'){
const lastRect = lines[lines.length - 1];
lastRect.setAttribute('fill', '#ffffff');
lastRect.setAttribute('stroke-width', 2);
lastRect.setAttribute('stroke', "#ffffff");
lastRect.setAttribute('stroke-dasharray', "0");
}
isDrawing = false;
if (lines.length > 0) {
const lastElement = lines.pop();
undoStack.push(lastElement);
redoStack = [];
}
});
undoBtn.addEventListener('click', () => {
if (undoStack.length > 0) {
const lastElement = undoStack.pop();
drawingBoard.removeChild(lastElement);
redoStack.push(lastElement);
}
});
redoBtn.addEventListener('click', () => {
if (redoStack.length > 0) {
const lastElement = redoStack.pop();
drawingBoard.appendChild(lastElement);
undoStack.push(lastElement);
}
});
function handleCanvasClick(e) {
if (currentTool === 'text') {
showTextInput(e.offsetX, e.offsetY);
}
}
function showTextInput(x, y) {
textInputContainer.style.display = 'block';
textInputContainer.style.left = x + 'px';
textInputContainer.style.top = y + 'px';
textInput.focus();
}
// 4 -- 36
// 50 -- 87 = 37
// 1 ---13 == 12
function confirmText() {
const text = textInput.value.trim();
if (text) {
console.log(textInputContainer.style);
console.log(textInputContainer.style.left);
console.log(textInputContainer.style.top);
const x = parseInt(textInputContainer.style.left) ; //- rect.left
const y = parseInt(textInputContainer.style.top) ; // 稍微下移 - rect.top
const textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');
textElement.textContent = text;
textElement.setAttribute('x', x);
textElement.setAttribute('y', y);
textElement.setAttribute('font-size', 18+(sizeInput.value*1.5));
textElement.setAttribute('fill', $("#chooseColor").val());
textElement.setAttribute('opacity', opacityInput.value);
drawingBoard.appendChild(textElement);
lines.push(textElement);
if (lines.length > 0) {
const lastElement = lines.pop();
undoStack.push(lastElement);
redoStack = [];
}
}
cancelText();
}
function cancelText() {
textInput.value = '';
textInputContainer.style.display = 'none';
}
</script>
</body>
</html>

被折叠的 条评论
为什么被折叠?



