Skip to content

Instantly share code, notes, and snippets.

@alexcmgit
Last active January 13, 2023 02:32
Show Gist options
  • Save alexcmgit/7102e9b813865c38fe32adcaa73682d6 to your computer and use it in GitHub Desktop.
Save alexcmgit/7102e9b813865c38fe32adcaa73682d6 to your computer and use it in GitHub Desktop.
Script that allows you to draw on any website (for sketching, study, etc.), it is a Tampermonkey script.
// ==UserScript==
// @name Draw on page using a pen or mouse
// @namespace http://tampermonkey.net/
// @version 0.1.0
// @description Hotkey based on page drawer.
// @author @alexrintt
// @match http*://*/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant none
// @license MIT
// ==/UserScript==
(function () {
window.onload = () => {
const CANVAS_ID = `__on__page__drawing__`
const SUPPORT_CANVAS_ID = `__on__page__drawing__support__`
const CANVAS_COMMON_CLASS = `__on__page__drawing__class__`
const RADIUS_CONFIG_LOCAL_KEY = `drawing__radius__size___`
const canvasStyle = `
<style>
.${CANVAS_COMMON_CLASS} {
touch-action: none;
box-sizing: border-box;
position: fixed;
top: 0;.
left: 0;
bottom: 0;
right: 0;
z-index: 10000;
width: 100%;
height: 100%;
visibility: hidden;
}
#${CANVAS_ID} {
background: rgba(0,0,0,0.4);
}
</style>
`
const canvasHtml = `
<canvas class="${CANVAS_COMMON_CLASS}" id="${CANVAS_ID}"></canvas>
<canvas class="${CANVAS_COMMON_CLASS}" id="${SUPPORT_CANVAS_ID}"></canvas>
`
window.document.body.insertAdjacentHTML(`beforeend`, canvasHtml)
window.document.head.insertAdjacentHTML(`beforeend`, canvasStyle)
const canvas = window.document.getElementById(CANVAS_ID)
const support = window.document.getElementById(SUPPORT_CANVAS_ID)
const canvasPhysicalSize = canvas.getBoundingClientRect()
const supportPhysicalSize = support.getBoundingClientRect()
canvas.width = canvasPhysicalSize.width
canvas.height = canvasPhysicalSize.height
support.width = supportPhysicalSize.width
support.height = supportPhysicalSize.height
let recording = false
const ctx = canvas.getContext(`2d`)
const sctx = support.getContext(`2d`)
let stack = []
let lastPos = undefined
let radius = Number(window.localStorage.getItem(RADIUS_CONFIG_LOCAL_KEY)) ||
(() => {
window.localStorage.setItem(RADIUS_CONFIG_LOCAL_KEY, 5)
return 5
})()
function drawPointerCircle(x, y) {
if (!isCanvasVisible()) return
sctx.clearRect(0, 0, support.width, support.height)
sctx.beginPath()
sctx.strokeStyle = 'orange';
sctx.arc(x, y, radius, 0, Math.PI * 2, false);
sctx.stroke()
}
function getPointerEventCoord(e) {
let x, y
switch (e.pointerType) {
case `mouse`:
x = e.clientX
y = e.clientY
break
case `pen`:
x = e.clientX
y = e.clientY
console.log({
e
})
break
}
return {
x,
y
}
}
function record(e) {
e.stopPropagation()
const {
x,
y
} = getPointerEventCoord(e)
lastPos = {
x,
y
}
const hasPenPressureWhenApplicable = e.pointerType === `pen` ? e
.pressure > 0 : true
drawPointerCircle(x, y)
if (!recording || !hasPenPressureWhenApplicable) return
pushStackPoint(x, y)
}
function clearCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
function drawStack() {
const entries = stack.filter(e => e.enabled)
for (const entry of entries) {
const points = entry.points.filter(p => p && p.x && p.y)
const radiusAvg = entry.points.map(p => p.radius || 0).reduce((acc,
e) => acc + e, 0) / entry.points.length
if (points.length) {
ctx.lineWidth = radiusAvg
ctx.strokeStyle = "rgb(255, 255, 255)"
ctx.fillStyle = "rgb(255, 255, 255)"
drawPoints(ctx, points)
}
}
}
// https://stackoverflow.com/a/10568043/11793117.
function drawPoints(ctx, points) {
// draw a basic circle instead
if (points.length < 6) {
var b = points[0];
ctx.beginPath();
ctx.arc(b.x, b.y, ctx.lineWidth / 2, 0, Math.PI * 2, !0);
ctx.closePath();
ctx.fill();
return
}
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
// draw a bunch of quadratics, using the average of two points as the control point
let i = 1
for (; i < points.length - 2; i++) {
var c = (points[i].x + points[i + 1].x) / 2,
d = (points[i].y + points[i + 1].y) / 2;
ctx.quadraticCurveTo(points[i].x, points[i].y, c, d)
}
ctx.quadraticCurveTo(points[i].x, points[i].y, points[i + 1].x, points[i +
1].y);
ctx.stroke();
}
const KEY_Z = 90
const KEY_Y = 89
const KEY_ESC = 27
const KEY_BACKSPACE = 8
const KEY_D = 68
const KEY_NUMPAD_PLUS = 107
const KEY_NUMPAD_MINUS = 109
const KEY_C = 67
let visible = false
function hideCanvas() {
visible = false
canvas.style.visibility = "hidden"
support.style.visibility = "hidden"
}
function showCanvas() {
visible = true
canvas.style.visibility = "visible"
support.style.visibility = "visible"
}
function isCanvasVisible() {
return visible
}
function toggleCanvasVisibility() {
if (isCanvasVisible()) {
hideCanvas()
} else {
showCanvas()
}
}
function onKeyUp() { }
function onKeyDown(e) {
if (e.keyCode === KEY_ESC || e.keyCode === KEY_BACKSPACE) {
clearStack()
} else {
if (e.shiftKey && e.ctrlKey) {
if (e.keyCode === KEY_Z) {
redo()
}
} else if (e.shiftKey) {
if (e.keyCode === KEY_C) {
clearStack()
} else if (e.keyCode === KEY_D) {
toggleCanvasVisibility()
} else if (e.keyCode === KEY_NUMPAD_PLUS) {
radius += 2
drawPointerCircle(lastPos.x, lastPos.y)
window.localStorage.setItem(RADIUS_CONFIG_LOCAL_KEY, radius)
} else if (e.keyCode === KEY_NUMPAD_MINUS) {
radius -= 2
drawPointerCircle(lastPos.x, lastPos.y)
window.localStorage.setItem(RADIUS_CONFIG_LOCAL_KEY, radius)
}
} else if (e.ctrlKey) {
if (e.keyCode === KEY_Z) {
undo()
} else if (e.keyCode === KEY_Y) {
redo()
}
}
}
}
function redraw() {
clearCanvas()
drawStack()
}
function undo() {
const last = stack.map(e => e.enabled).lastIndexOf(true)
if (last === -1) return // ignore, there no drawing undo.
stack[last].enabled = false
redraw()
}
function clearStack() {
stack = []
redraw()
}
function redo() {
const first = stack.findIndex(e => !e.enabled)
if (first === -1) return // There nothing to redo.
stack[first].enabled = true
redraw()
}
function pushStackPoint(x, y) {
if (!recording || !isCanvasVisible()) return
// Remove disabled old entries since this current push call overrides it.
stack = stack.filter(e => e.enabled)
if (!stack.length) pushStackEntry()
stack[stack.length - 1] = {
...stack[stack.length - 1],
points: [...((stack[stack.length - 1] || {}).points || []), {
x,
y,
radius
}]
}
redraw()
}
function pushStackEntry() {
stack.push({
points: [],
enabled: true
})
}
function startRecording() {
recording = true
pushStackEntry()
}
function stopRecording() {
recording = false
}
window.addEventListener('pointerdown', startRecording)
window.addEventListener('pointermove', record)
window.addEventListener('pointerup', stopRecording)
window.addEventListener('keydown', onKeyDown)
window.addEventListener('keyup', onKeyUp)
window.onclick = console.log
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment