Skip to content

Instantly share code, notes, and snippets.

@syt-honey
Last active January 2, 2024 07:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save syt-honey/3823d059e16dbe8dfc150b18169e822a to your computer and use it in GitHub Desktop.
Save syt-honey/3823d059e16dbe8dfc150b18169e822a to your computer and use it in GitHub Desktop.
svg track
import { DrawLine } from './svg'
import { useRef, useState, useEffect, useCallback } from 'react'
export type BoardType = DrawLine
function App(): JSX.Element {
const svgRef = useRef<SVGSVGElement | null>(null)
const [board, setBoard] = useState<BoardType | null>(null)
useEffect(() => {
let currentBoard: BoardType | null = null
if (svgRef.current) {
currentBoard = new DrawLine(svgRef.current)
}
if (currentBoard) {
setBoard(currentBoard)
}
return (): void => {
currentBoard?.destroy()
}
}, [])
const clear = useCallback(() => {
if (svgRef.current) {
while (svgRef.current.firstChild) {
svgRef.current.removeChild(svgRef.current.firstChild)
}
}
}, [svgRef.current])
return (
<div className="container">
<button className="clear-btn" onClick={clear}>
Clear
</button>
<svg ref={svgRef} fill={board?.fill} stroke={board?.color} strokeWidth={board?.weight}></svg>
</div>
)
}
export default App
#root {
height: 100vh;
}
.container {
color: 'red';
height: 100%;
border: 3px solid #00ff00;
box-sizing: border-box;
overflow: hidden;
-webkit-app-region: no-drag;
user-select: none;
> svg {
width: 100%;
height: 100%;
}
}
.clear-btn {
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
}
export function input(el: SVGSVGElement, { start, update, finish }): { unsubscribe: () => void } {
function down(ev): void {
if (ev.button !== 0) return
el.setPointerCapture(ev.pointerId)
el.addEventListener('pointermove', move)
start()
}
function move(ev): void {
update(ev)
}
function up(ev): void {
finish()
el.removeEventListener('pointermove', move)
el.releasePointerCapture(ev.pointerId)
}
el.addEventListener('pointerdown', down)
el.addEventListener('pointerup', up)
return {
unsubscribe(): void {
el.removeEventListener('pointerdown', down)
el.removeEventListener('pointerup', up)
}
}
}
// create window
const mainWindow = new BrowserWindow({
width: 900,
height: 670,
show: false,
autoHideMenuBar: true,
frame: false,
alwaysOnTop: true,
transparent: true,
resizable: false,
...
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false
}
})
// set window visible on all workspaces
mainWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: false })
import { input } from './input'
export class DrawLine {
el: SVGSVGElement | null = null
targetEvent: { unsubscribe: () => void } | null = null
path: Line | null = null
color: string = 'red'
weight: string = '4'
fill: string = 'none'
constructor(el: SVGSVGElement) {
this.el = el
this.el.style.color = this.color
this.targetEvent = input(this.el, {
start: this.start.bind(this),
update: this.update.bind(this),
finish: this.finish.bind(this)
})
}
start(): void {
this.path = new Line(svg('path'))
if (this.el && this.path) {
this.el.append(this.path.path)
}
}
update(ev: PointerEvent): void {
const { offsetX: x, offsetY: y } = ev
if (this.path) {
this.path.update({ x, y })
}
}
finish(): void {
this.path = null
}
destroy(): void {
if (this.targetEvent) {
this.targetEvent.unsubscribe()
}
this.el = null
}
}
export class Line {
path: SVGElement
last: Point | null = null
defn: string = ''
tail: number = 0
constructor(path: SVGElement) {
this.path = path
}
remove(): void {
this.path.remove()
}
update(point): void {
if (this.last) {
if (this.tail) {
this.defn = this.defn.slice(0, -this.tail)
this.defn += Q(this.last, mid(this.last, point))
} else {
this.defn += L(mid(this.last, point))
}
const tail = L(point)
this.tail = tail.length
this.defn += tail
this.path.setAttribute('d', this.defn)
this.last = point
} else {
this.defn = M((this.last = point))
}
}
}
export type Point = { x: number; y: number }
export function mid(a, b): Point {
return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }
}
export const M = ({ x, y }): string => `M${x.toFixed(2)},${y.toFixed(2)}`
export const L = ({ x, y }): string => `L${x.toFixed(2)},${y.toFixed(2)}`
export const Q = (c, { x, y }): string =>
`Q${c.x.toFixed(2)},${c.y.toFixed(2)} ${x.toFixed(2)},${y.toFixed(2)}`
export function svg(tag = 'svg'): SVGElement {
return document.createElementNS('http://www.w3.org/2000/svg', tag)
}
@syt-honey
Copy link
Author

syt-honey commented Dec 29, 2023

The computer I use is: MacOS x86 Sonoma 14.2.1

electron: 25.6.0

@syt-honey
Copy link
Author

syt-honey commented Dec 29, 2023

New: This trace is behind the background. If you add background to the element, the trace is not visible.

We can use a background with alpha to confirm this:

.container {
    // set this, and adjust alpha
    background-color: rgba(0, 0, 0, .5); 
    > svg {
        ...
    }
}

@syt-honey
Copy link
Author

@syt-honey
Copy link
Author

syt-honey commented Dec 29, 2023

WechatIMG1330

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment