├── .gitignore ├── LICENSE ├── README.md ├── assets ├── icons │ ├── icon-eraser.svg │ ├── icon-pen.svg │ ├── trashcan.svg │ ├── width-1.svg │ ├── width-2.svg │ ├── width-3.svg │ └── width-4.svg └── src │ ├── icon-eraser.afdesign │ ├── icon-pen.afdesign │ ├── width-1.afdesign │ ├── width-2.afdesign │ ├── width-3.afdesign │ └── width-4.afdesign ├── core.js ├── index.html └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 戴兜 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # douBoard 2 | 3 | > 一个支持压感的在线白板 4 | 5 | ## 快捷键 6 | 7 | - Ctrl+S 保存 8 | - Ctrl+Z 撤回 9 | - E 橡皮擦 10 | - B 画笔 11 | 12 | ## 在线demo 13 | 14 | [https://ink.daidr.me](https://ink.daidr.me "demo") -------------------------------------------------------------------------------- /assets/icons/icon-eraser.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/icon-pen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /assets/icons/trashcan.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/width-1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/width-2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/width-3.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/width-4.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/src/icon-eraser.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daidr/douBoard/136b5105267f5772daeafe59bd80ffc3836b3a8e/assets/src/icon-eraser.afdesign -------------------------------------------------------------------------------- /assets/src/icon-pen.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daidr/douBoard/136b5105267f5772daeafe59bd80ffc3836b3a8e/assets/src/icon-pen.afdesign -------------------------------------------------------------------------------- /assets/src/width-1.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daidr/douBoard/136b5105267f5772daeafe59bd80ffc3836b3a8e/assets/src/width-1.afdesign -------------------------------------------------------------------------------- /assets/src/width-2.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daidr/douBoard/136b5105267f5772daeafe59bd80ffc3836b3a8e/assets/src/width-2.afdesign -------------------------------------------------------------------------------- /assets/src/width-3.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daidr/douBoard/136b5105267f5772daeafe59bd80ffc3836b3a8e/assets/src/width-3.afdesign -------------------------------------------------------------------------------- /assets/src/width-4.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daidr/douBoard/136b5105267f5772daeafe59bd80ffc3836b3a8e/assets/src/width-4.afdesign -------------------------------------------------------------------------------- /core.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | const icons = { 3 | "pen": function (color) { 4 | return ``; 5 | }, 6 | "eraser": function () { 7 | return ``; 8 | }, 9 | "width-1": function (color) { 10 | return ``; 11 | }, 12 | "width-2": function (color) { 13 | return ``; 14 | }, 15 | "width-3": function (color) { 16 | return ``; 17 | }, 18 | "width-4": function (color) { 19 | return ``; 20 | }, 21 | } 22 | let canvas = document.querySelector("#mainCanvas"); 23 | let ctx = canvas.getContext("2d"); 24 | 25 | let offCanvas = document.createElement("canvas"); 26 | let offCtx = offCanvas.getContext("2d"); 27 | 28 | let toolbarPen = document.querySelector(".toolbar-pen"); 29 | let toolbarEraser = document.querySelector(".toolbar-eraser"); 30 | let toolbarPenOnly = document.querySelector(".toolbar-penonly"); 31 | let toolbarPenMenu = document.querySelector(".toolbarmenu-pen"); 32 | let toolbarEraserMenu = document.querySelector(".toolbarmenu-eraser"); 33 | 34 | let eraser = document.querySelector(".eraser"); 35 | let isPenOnly = false; 36 | for (i in document.images) document.images[i].ondragstart = function () { return false; }; 37 | 38 | window.onresize = function () { 39 | offCanvas.width = canvas.width; 40 | offCanvas.height = canvas.height; 41 | offCtx.drawImage(canvas, 0, 0); 42 | 43 | canvas.width = document.documentElement.clientWidth; 44 | canvas.height = document.documentElement.clientHeight; 45 | 46 | let width = canvas.width, height = canvas.height; 47 | if (window.devicePixelRatio) { 48 | canvas.style.width = width + "px"; 49 | canvas.style.height = height + "px"; 50 | canvas.height = height * window.devicePixelRatio; 51 | canvas.width = width * window.devicePixelRatio; 52 | ctx.scale(window.devicePixelRatio, window.devicePixelRatio); 53 | } 54 | ctx.drawImage(offCanvas, 0, 0); 55 | ctx.lineJoin = "round"; 56 | ctx.lineCap = "round"; 57 | } 58 | 59 | window.onresize(); 60 | 61 | canDraw = false; 62 | let baseLineList = [6, 10, 15, 25]; 63 | let baseLineMode = 0; 64 | let lineColorList = ["#000", "#5B2D90", "#0069BF", "#F6630C", "#AB228B", "#B7B7B7", "#E3E3E3", "#E71224", "#D20078", "#02A556", "#C09E66", "#FFC114"]; //线条颜色列表 65 | let lineColorMode = 0; 66 | let history = []; 67 | let priviousDraw = 0; 68 | let priviousPressure = 0; 69 | 70 | for (let i = 0; i < 4; i++) { 71 | document.querySelector(`.width-switcher-${i + 1}`).onpointerup = function () { setPenWidth(i) } 72 | } 73 | 74 | for (let i = 0; i < 12; i++) { 75 | let child = document.createElement("div"); 76 | child.classList.add("color-switcher"); 77 | child.style.backgroundColor = lineColorList[i]; 78 | child.onpointerup = function () { 79 | setPenColor(i) 80 | } 81 | if (i == 0) { 82 | child.classList.add("active"); 83 | } 84 | document.querySelector(`.switcher-container[type="color"]`).appendChild(child) 85 | } 86 | 87 | function setPenWidth(mode) { 88 | baseLineMode = mode; 89 | let viewerContainer = document.querySelector(".width-viewer"); 90 | viewerContainer.innerHTML = icons[`width-${mode + 1}`](lineColorList[lineColorMode]); 91 | toolbarPen.innerHTML = icons.pen(lineColorList[lineColorMode]) + ``; 92 | document.querySelector(`.switcher-container[type="width"] .active`).classList.remove("active"); 93 | document.querySelector(`.width-switcher-${mode + 1}`).classList.add("active"); 94 | } 95 | 96 | setPenWidth(0); 97 | 98 | function setPenColor(mode) { 99 | lineColorMode = mode; 100 | toolbarPen.innerHTML = icons.pen(lineColorList[mode]) + ``; 101 | setPenWidth(baseLineMode); 102 | document.querySelector(`.color-switcher.active`).classList.remove("active"); 103 | document.querySelectorAll(`.color-switcher`)[mode].classList.add("active"); 104 | } 105 | 106 | setPenColor(0); 107 | 108 | 109 | let points = []; 110 | let beginPoint = null; 111 | 112 | const drawMode = { 113 | "down": function (e) { 114 | if (isPenOnly && e.pointerType != "pen") return; 115 | setToolbarStatus(false); 116 | // writeHistory(); 117 | canDraw = true; 118 | ctx.globalCompositeOperation = "source-over"; 119 | ctx.strokeStyle = lineColorList[lineColorMode]; 120 | const { x, y, pressure } = getPos(e); 121 | priviousPressure = pressure; 122 | points.push({ x, y }); 123 | beginPoint = { x, y }; 124 | }, 125 | "up": function (e) { 126 | if (!canDraw) return; 127 | if (isPenOnly && e.pointerType != "pen") return; 128 | setToolbarStatus(true); 129 | const { x, y, pressure } = getPos(e); 130 | 131 | points.push({ x, y }); 132 | 133 | if (points.length > 3) { 134 | const lastTwoPoints = points.slice(-2); 135 | const controlPoint = lastTwoPoints[0]; 136 | const endPoint = lastTwoPoints[1]; 137 | usePen(beginPoint, controlPoint, endPoint, (priviousPressure + pressure) / 2 * baseLineList[baseLineMode]); 138 | } else { 139 | priviousPressure = pressure; 140 | } 141 | beginPoint = null; 142 | canDraw = false; 143 | points = []; 144 | }, 145 | "move": function (e) { 146 | if (isPenOnly && e.pointerType != "pen") return; 147 | if (!canDraw) return; 148 | const { x, y, pressure } = getPos(e); 149 | points.push({ x, y }); 150 | 151 | if (points.length > 3) { 152 | const lastTwoPoints = points.slice(-2); 153 | const controlPoint = lastTwoPoints[0]; 154 | const endPoint = { 155 | x: (lastTwoPoints[0].x + lastTwoPoints[1].x) / 2, 156 | y: (lastTwoPoints[0].y + lastTwoPoints[1].y) / 2, 157 | } 158 | usePen(beginPoint, controlPoint, endPoint, pressure * baseLineList[baseLineMode]); 159 | beginPoint = endPoint; 160 | } 161 | } 162 | } 163 | 164 | const eraserMode = { 165 | "down": function (e) { 166 | setToolbarStatus(false); 167 | // writeHistory(); 168 | canDraw = true; 169 | ctx.strokeStyle = "rgba(0,0,0,1)"; 170 | ctx.globalCompositeOperation = "destination-out"; 171 | const { x, y } = getPos(e); 172 | eraser.style.top = `${y - 30}px`; 173 | eraser.style.left = `${x - 30}px`; 174 | eraser.style.display = "block"; 175 | points.push({ x, y }); 176 | beginPoint = { x, y }; 177 | }, 178 | "up": function (e) { 179 | if (!canDraw) return; 180 | setToolbarStatus(true); 181 | const { x, y } = getPos(e); 182 | 183 | points.push({ x, y }); 184 | 185 | if (points.length > 3) { 186 | const lastTwoPoints = points.slice(-2); 187 | const controlPoint = lastTwoPoints[0]; 188 | const endPoint = lastTwoPoints[1]; 189 | useEraser(beginPoint, controlPoint, endPoint, 60); 190 | } 191 | beginPoint = null; 192 | canDraw = false; 193 | eraser.style.display = "none"; 194 | points = []; 195 | }, 196 | "move": function (e) { 197 | if (!canDraw) return; 198 | const { x, y } = getPos(e); 199 | points.push({ x, y }); 200 | 201 | if (points.length > 3) { 202 | const lastTwoPoints = points.slice(-2); 203 | const controlPoint = lastTwoPoints[0]; 204 | const endPoint = { 205 | x: (lastTwoPoints[0].x + lastTwoPoints[1].x) / 2, 206 | y: (lastTwoPoints[0].y + lastTwoPoints[1].y) / 2, 207 | } 208 | eraser.style.top = `${y - 30}px`; 209 | eraser.style.left = `${x - 30}px`; 210 | useEraser(beginPoint, controlPoint, endPoint, 60); 211 | beginPoint = endPoint; 212 | } 213 | } 214 | } 215 | 216 | canvas.addEventListener("pointerdown", drawMode["down"], { passive: true }) 217 | canvas.addEventListener("pointerup", drawMode["up"], { passive: true }) 218 | canvas.addEventListener("pointermove", drawMode["move"], { passive: true }) 219 | 220 | function getPos(evt) { 221 | return { 222 | x: evt.clientX, 223 | y: evt.clientY, 224 | pressure: evt.pressure 225 | } 226 | } 227 | function usePen(beginPoint, controlPoint, endPoint, width) { 228 | ctx.beginPath(); 229 | ctx.moveTo(beginPoint.x, beginPoint.y); 230 | ctx.quadraticCurveTo(controlPoint.x, controlPoint.y, endPoint.x, endPoint.y); 231 | ctx.lineWidth = width; 232 | ctx.stroke(); 233 | ctx.closePath(); 234 | } 235 | 236 | function useEraser(beginPoint, controlPoint, endPoint, width) { 237 | ctx.beginPath(); 238 | ctx.moveTo(beginPoint.x, beginPoint.y); 239 | ctx.quadraticCurveTo(controlPoint.x, controlPoint.y, endPoint.x, endPoint.y); 240 | ctx.lineWidth = width; 241 | ctx.stroke(); 242 | } 243 | 244 | toolbarEraser.innerHTML = icons.eraser(); 245 | 246 | document.querySelector(".clearAll").onpointerup = function () { 247 | ctx.clearRect(0, 0, canvas.width, canvas.height); 248 | toolbarEraserMenu.classList.remove("active"); 249 | } 250 | toolbarPen.onpointerup = function () { 251 | toolbarEraserMenu.classList.remove("active"); 252 | if (toolbarPen.classList.contains("active")) { 253 | toolbarPenMenu.classList.toggle("active"); 254 | } 255 | toolbarPen.classList.add("active"); 256 | toolbarEraser.classList.remove("active"); 257 | canvas.removeEventListener("pointerdown", eraserMode["down"], { passive: true }) 258 | canvas.removeEventListener("pointerup", eraserMode["up"], { passive: true }) 259 | canvas.removeEventListener("pointermove", eraserMode["move"], { passive: true }) 260 | canvas.removeEventListener("pointerdown", drawMode["down"], { passive: true }) 261 | canvas.removeEventListener("pointerup", drawMode["up"], { passive: true }) 262 | canvas.removeEventListener("pointermove", drawMode["move"], { passive: true }) 263 | canvas.addEventListener("pointerdown", drawMode["down"], { passive: true }) 264 | canvas.addEventListener("pointerup", drawMode["up"], { passive: true }) 265 | canvas.addEventListener("pointermove", drawMode["move"], { passive: true }) 266 | } 267 | toolbarEraser.onpointerup = function () { 268 | toolbarPenMenu.classList.remove("active"); 269 | if (toolbarEraser.classList.contains("active")) { 270 | toolbarEraserMenu.classList.toggle("active"); 271 | } 272 | toolbarEraser.classList.add("active"); 273 | toolbarPen.classList.remove("active"); 274 | canvas.removeEventListener("pointerdown", eraserMode["down"], { passive: true }) 275 | canvas.removeEventListener("pointerup", eraserMode["up"], { passive: true }) 276 | canvas.removeEventListener("pointermove", eraserMode["move"], { passive: true }) 277 | canvas.removeEventListener("pointerdown", drawMode["down"], { passive: true }) 278 | canvas.removeEventListener("pointerup", drawMode["up"], { passive: true }) 279 | canvas.removeEventListener("pointermove", drawMode["move"], { passive: true }) 280 | canvas.addEventListener("pointerdown", eraserMode["down"], { passive: true }) 281 | canvas.addEventListener("pointerup", eraserMode["up"], { passive: true }) 282 | canvas.addEventListener("pointermove", eraserMode["move"], { passive: true }) 283 | } 284 | 285 | toolbarPenOnly.onpointerup = function () { 286 | isPenOnly = !isPenOnly; 287 | if (isPenOnly) { 288 | toolbarPenOnly.classList.add("enabled"); 289 | } else { 290 | toolbarPenOnly.classList.remove("enabled"); 291 | } 292 | } 293 | 294 | 295 | window.onkeyup = function (e) { 296 | if (e.ctrlKey == true && e.keyCode == 83) { //Ctrl+S 保存 297 | e.preventDefault(); 298 | e.returnvalue = false; 299 | saveCanvas(); 300 | } 301 | if (e.keyCode == 69) { //E 橡皮擦 302 | e.returnvalue = false; 303 | toolbarEraser.onpointerup(); 304 | } 305 | if (e.keyCode == 66) { //B 笔 306 | e.returnvalue = false; 307 | toolbarPen.onpointerup(); 308 | } 309 | if (e.ctrlKey == true && e.keyCode == 90) { //Ctrl+Z 撤销 310 | e.returnvalue = false; 311 | e.preventDefault(); 312 | let content = popHistory(); 313 | if (content) { 314 | ctx.putImageData(content, 0, 0); 315 | } 316 | } 317 | } 318 | 319 | function writeHistory() { 320 | if (history.length > 15) { 321 | history.shift() 322 | } 323 | if (priviousDraw == 0) { 324 | priviousDraw = new Date().getTime(); 325 | history.push(ctx.getImageData(0, 0, canvas.width, canvas.height)); 326 | } else { 327 | if (new Date().getTime() - priviousDraw > 1000) { 328 | history.push(ctx.getImageData(0, 0, canvas.width, canvas.height)); 329 | } 330 | } 331 | } 332 | 333 | function popHistory() { 334 | if (history.length == 0) { 335 | return false; 336 | } else { 337 | return history.pop(history); 338 | } 339 | } 340 | 341 | function saveCanvas() { 342 | var link = document.createElement("a"); 343 | var imgData = canvas.toDataURL(); 344 | var blob = dataURLtoBlob(imgData); 345 | var objURL = URL.createObjectURL(blob); 346 | link.download = `DouBoard(${new Date().toLocaleString().replace(/\//g, "-")}).png`; 347 | link.href = objURL; 348 | link.click(); 349 | link.remove(); 350 | 351 | setTimeout(function () { URL.revokeObjectURL(objURL); }, 5000); 352 | 353 | function dataURLtoBlob(dataurl) { 354 | var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], 355 | bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n); 356 | while (n--) { 357 | u8arr[n] = bstr.charCodeAt(n); 358 | } 359 | return new Blob([u8arr], { type: mime }); 360 | } 361 | } 362 | 363 | function setToolbarStatus(status) { 364 | let toolbarContainer = document.querySelector("#toolbar-container"); 365 | if (!status) { 366 | toolbarContainer.classList.add("untouchable"); 367 | } else { 368 | toolbarContainer.classList.remove("untouchable"); 369 | } 370 | } 371 | })() 372 | 373 | 374 | document.addEventListener("touchstart", function (e) { 375 | e.preventDefault(); 376 | }, { passive: false }); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | douBoard 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 |
18 |
19 |
橡皮
20 |
21 | 22 | 25 | 26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | 35 |
36 |
37 | 38 |
39 |
40 | 41 |
42 |
43 | 44 |
45 |
46 |
47 |
48 |
49 |
50 |

清空画布

51 |
52 |
53 | 64 | 97 |
98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | width: 100%; 4 | height: 100%; 5 | padding: 0; 6 | margin: 0; 7 | -webkit-user-select: none; 8 | user-select: none; 9 | } 10 | 11 | * { 12 | -webkit-user-select: none; 13 | user-select: none; 14 | } 15 | 16 | canvas { 17 | position: fixed; 18 | top: 0; 19 | left: 0; 20 | touch-action: none; 21 | } 22 | 23 | .eraser { 24 | border-radius: 100%; 25 | width: 60px; 26 | height: 60px; 27 | border: 2px #d3d3d3 solid; 28 | background: rgba(207, 207, 207, 0.2); 29 | opacity: 0.7; 30 | display: none; 31 | position: fixed; 32 | pointer-events: none; 33 | z-index: 10; 34 | box-sizing: border-box; 35 | } 36 | 37 | .toolbar { 38 | height: 50px; 39 | width: 150px; 40 | position: fixed; 41 | bottom: 0px; 42 | left: 50%; 43 | z-index: 12; 44 | transform: translateX(-50%); 45 | box-shadow: 0 0 10px 0px rgba(0, 0, 0, 0.2); 46 | user-select: none; 47 | background: #fff; 48 | } 49 | 50 | [class^="toolbar-"] { 51 | height: 50px; 52 | width: 50px; 53 | line-height: 50px; 54 | text-align: center; 55 | display: inline-block; 56 | float: left; 57 | cursor: pointer; 58 | transition: background-color 0.2s ease-out; 59 | } 60 | 61 | [class^="toolbar-"]:hover { 62 | background: #f1f1f2; 63 | } 64 | 65 | [class^="toolbar-"].active { 66 | background: #f1f1f2; 67 | } 68 | 69 | [class^="toolbar-"] svg { 70 | transform: translateY(12px); 71 | transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); 72 | } 73 | [class^="toolbar-"].active svg { 74 | transform: translateY(3px); 75 | } 76 | 77 | .toolbar-pen { 78 | position: relative; 79 | } 80 | 81 | .toolbar-pen span { 82 | opacity: 0; 83 | transition: opacity 0.1s ease-out; 84 | } 85 | 86 | .toolbar-pen.active span { 87 | opacity: 1; 88 | position: absolute; 89 | background: #000; 90 | top: 3px; 91 | right: 3px; 92 | border-radius: 100%; 93 | } 94 | 95 | .toolbar-pen-viewer-1 { 96 | width: 3px; 97 | height: 3px; 98 | } 99 | .toolbar-pen-viewer-2 { 100 | width: 5px; 101 | height: 5px; 102 | } 103 | .toolbar-pen-viewer-3 { 104 | width: 7px; 105 | height: 7px; 106 | } 107 | .toolbar-pen-viewer-4 { 108 | width: 9px; 109 | height: 9px; 110 | } 111 | 112 | .toolbar-eraser { 113 | } 114 | 115 | .toolbar-penonly .status { 116 | width: 7px; 117 | height: 7px; 118 | background-color: #13a559; 119 | position: absolute; 120 | top: 3px; 121 | right: 3px; 122 | border-radius: 100%; 123 | } 124 | .toolbar-penonly.enabled .status { 125 | background-color: #e31328; 126 | } 127 | 128 | .toolbarmenu { 129 | height: 50px; 130 | width: 100px; 131 | position: fixed; 132 | bottom: 50px; 133 | left: 50%; 134 | transform: translateX(-50%); 135 | user-select: none; 136 | z-index: 11; 137 | } 138 | 139 | [class^="toolbarmenu-"] { 140 | height: auto; 141 | width: 160px; 142 | background: #fff; 143 | box-shadow: 0 0 10px 0px rgba(0, 0, 0, 0.15); 144 | pointer-events: none; 145 | } 146 | 147 | [class^="toolbarmenu-"].active { 148 | pointer-events: initial; 149 | } 150 | 151 | .toolbarmenu-pen { 152 | opacity: 0; 153 | width: 260px; 154 | transition: opacity 0.2s linear; 155 | pointer-events: none; 156 | animation-fill-mode: forwards; 157 | animation: fadelogOut 0.4s; 158 | position: fixed; 159 | bottom: 5px; 160 | padding: 5px 5px 5px 0; 161 | } 162 | 163 | .width-viewer { 164 | height: 200px; 165 | width: 50px; 166 | float: left; 167 | } 168 | 169 | .switcher-container { 170 | height: 200px; 171 | float: left; 172 | display: flex; 173 | flex-direction: column; 174 | justify-content: space-around; 175 | } 176 | 177 | .switcher-container[type="width"] { 178 | width: 50px; 179 | } 180 | 181 | .switcher-container [class^="width-switcher-"] { 182 | height: 25px; 183 | width: 25px; 184 | border-radius: 100%; 185 | border: 2px solid rgba(179, 179, 179, 0.8); 186 | box-shadow: 0 0 0px 3px transparent; 187 | transition: box-shadow 0.1s ease-out; 188 | cursor: pointer; 189 | display: flex; 190 | justify-content: center; 191 | align-items: center; 192 | } 193 | 194 | .switcher-container [class^="width-switcher-"]:hover { 195 | box-shadow: 0 0 0px 3px rgba(221, 221, 221, 0.8); 196 | } 197 | 198 | .switcher-container [class^="width-switcher-"].active { 199 | box-shadow: 0 0 0px 3px rgba(221, 221, 221, 0.8); 200 | } 201 | 202 | .switcher-container [class^="width-switcher-"] span { 203 | background: #000; 204 | display: block; 205 | border-radius: 100%; 206 | } 207 | 208 | .switcher-container .width-switcher-4 span { 209 | height: 13px; 210 | width: 13px; 211 | } 212 | 213 | .switcher-container .width-switcher-3 span { 214 | height: 11px; 215 | width: 11px; 216 | } 217 | 218 | .switcher-container .width-switcher-2 span { 219 | height: 9px; 220 | width: 9px; 221 | } 222 | 223 | .switcher-container .width-switcher-1 span { 224 | height: 5px; 225 | width: 5px; 226 | } 227 | 228 | .switcher-container[type="color"] { 229 | width: 150px; 230 | flex-flow: column wrap; 231 | } 232 | 233 | .switcher-container .color-switcher { 234 | height: 25px; 235 | width: 25px; 236 | border-radius: 100%; 237 | border: 2px solid rgba(179, 179, 179, 0.8); 238 | box-shadow: 0 0 0px 3px transparent; 239 | transition: box-shadow 0.1s ease-out; 240 | cursor: pointer; 241 | flex-shrink: 0; 242 | background-color: white; 243 | margin: 10px 10px; 244 | } 245 | 246 | .switcher-container .color-switcher:hover { 247 | box-shadow: 0 0 0px 3px rgba(221, 221, 221, 0.8); 248 | } 249 | 250 | .switcher-container .color-switcher.active { 251 | box-shadow: 0 0 0px 3px rgba(221, 221, 221, 0.8); 252 | } 253 | 254 | .toolbarmenu-pen.active { 255 | pointer-events: all; 256 | opacity: 1; 257 | animation-fill-mode: forwards; 258 | animation: fadelogIn 0.3s; 259 | } 260 | 261 | .toolbarmenu-eraser { 262 | opacity: 0; 263 | transition: opacity 0.2s linear; 264 | pointer-events: none; 265 | animation-fill-mode: forwards; 266 | animation: fadelogOut 0.4s; 267 | margin-left: 50px; 268 | } 269 | 270 | .toolbarmenu-eraser.active { 271 | pointer-events: all; 272 | opacity: 1; 273 | animation-fill-mode: forwards; 274 | animation: fadelogIn 0.3s; 275 | } 276 | 277 | .toolbarmenu-eraser p { 278 | cursor: pointer; 279 | height: 45px; 280 | margin: 0; 281 | padding: 0; 282 | display: flex; 283 | align-items: center; 284 | background: #fff; 285 | transition: background-color 0.1s ease-out; 286 | } 287 | 288 | .toolbarmenu-eraser p:hover { 289 | background: #f1f1f2; 290 | } 291 | 292 | .toolbarmenu-eraser p img { 293 | float: left; 294 | margin-right: 7px; 295 | margin-left: 10px; 296 | transform: scale(0.8); 297 | } 298 | 299 | @keyframes fadelogIn { 300 | 0% { 301 | transform: translate3d(0, 80px, 0) scale(0.7); 302 | } 303 | 100% { 304 | transform: none; 305 | } 306 | } 307 | 308 | @keyframes fadelogOut { 309 | 0% { 310 | transform: none; 311 | } 312 | 100% { 313 | transform: translate3d(0, 80px, 0) scale(0.7); 314 | } 315 | } 316 | 317 | #toolbar-container { 318 | transition: opacity 0.2s ease-out; 319 | } 320 | 321 | .untouchable { 322 | pointer-events: none; 323 | opacity: 0.7; 324 | } 325 | 326 | .untouchable .active { 327 | pointer-events: none !important; 328 | } 329 | --------------------------------------------------------------------------------