Last active
January 2, 2024 07:19
-
-
Save syt-honey/3823d059e16dbe8dfc150b18169e822a to your computer and use it in GitHub Desktop.
svg track
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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%); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 }) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
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 {
...
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The computer I use is: MacOS x86 Sonoma 14.2.1
electron: 25.6.0