Draw on page using a pen or mouse

Hotkey based on page drawer.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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.x
          y = e.y
          break
        case `pen`:
          x = e.x
          y = e.y
          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
  }
})();