Created
February 25, 2022 22:40
-
-
Save fronterior/54330e65d1830c0965541220764a5817 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 type PointerType = 'pointerdown'|'pointermove'|'pointerup'|'scroll'; | |
export interface CursorCoordinate { | |
type: PointerType; | |
x: number; | |
y: number; | |
dx?: number; | |
dy?: number; | |
} | |
export const drag = new class Drag { | |
handlerDown: Set<(pos: CursorCoordinate, event: PointerEvent) => void> = new Set; | |
handlerMove: Set<(pos: CursorCoordinate, event: PointerEvent) => void> = new Set; | |
handlerUp: Set<(pos: CursorCoordinate, event: PointerEvent) => void> = new Set; | |
ox: number; | |
oy: number; | |
dx: number; | |
dy: number; | |
isPushed: boolean = false; | |
constructor() { | |
document.addEventListener('pointerdown', ev => { | |
this.isPushed = true; | |
this.ox = ev.clientX; | |
this.oy = ev.clientY; | |
this.handlerDown.forEach(f => f({type: <PointerType>ev.type, x: ev.clientX, y: ev.clientY}, ev)); | |
}); | |
document.addEventListener('pointermove', ev => { | |
if (!this.isPushed) return; | |
this.dx = ev.clientX - this.ox; | |
this.dy = ev.clientY - this.oy; | |
this.ox = ev.clientX; | |
this.oy = ev.clientY; | |
this.handlerMove.forEach(f => f({type: <PointerType>ev.type, x: this.ox, y: this.oy, dx: this.dx, dy: this.dy}, ev)); | |
}); | |
document.addEventListener('pointerup', ev => { | |
this.isPushed = false; | |
this.ox = ev.clientX; | |
this.oy = ev.clientY; | |
this.handlerUp.forEach(f => f({type: <PointerType>ev.type, x: this.ox, y: this.oy}, ev)); | |
}); | |
} | |
} | |
export const downHandlers = drag.handlerDown; | |
export const moveHandlers = drag.handlerMove; | |
export const upHandlers = drag.handlerUp; | |
export class Selection { | |
el: HTMLDivElement; | |
isShow: boolean = false; | |
constructor( | |
public x: number = 0, | |
public y: number = 0, | |
public w: number = 0, | |
public h: number = 0, | |
public background: string = 'cyan', | |
public border: string = '1px solid rgba(0, 0, 0, 0.5)', | |
public opacity: number = 0.5, | |
) { | |
document.querySelectorAll('.hashsnap-custom-selection').forEach(el => el.parentNode.removeChild(el)); | |
this.el = Object.assign(document.createElement('div'), {className: 'hashsnap-custom-selection', x, y, w, h, background, border, opacity, style: 'position: absolute; pointer-events: none;'}); | |
const {style} = this.el; | |
style.background = this.background; | |
style.border = this.border; | |
style.opacity = <any>this.opacity; | |
style.transformOrigin = '0 0'; | |
document.body.appendChild(this.el); | |
} | |
show() { | |
this.isShow = true; | |
this.render(); | |
} | |
hide() { | |
this.isShow = false; | |
this.render(); | |
} | |
render() { | |
const {style} = this.el; | |
if (this.w < 0 && this.h < 0) style.transform = 'scale(-1, -1)'; | |
else if (this.w < 0) style.transform = 'scale(-1, 1)'; | |
else if (this.h < 0) style.transform = 'scale(1, -1)'; | |
else style.transform = 'scale(1, 1)'; | |
style.display = this.isShow ? 'block' : 'none'; | |
} | |
move(x: number, y: number) { | |
const {style} = this.el; | |
style.left = (this.x = x) + 'px'; | |
style.top = (this.y = y) + 'px'; | |
this.render(); | |
} | |
resize(w: number, h: number) { | |
const {style} = this.el; | |
style.width = Math.abs(this.w = w) + 'px'; | |
style.height = Math.abs(this.h = h) + 'px'; | |
this.render(); | |
} | |
draw(x: number, y: number, w: number, h: number) { | |
const {style} = this.el; | |
style.left = (this.x = x) + 'px'; | |
style.top = (this.y = y) + 'px'; | |
style.width = Math.abs(this.w = w) + 'px'; | |
style.height = Math.abs(this.h = h) + 'px'; | |
this.render(); | |
} | |
isPointInSelection(x: number, y: number) { | |
return between(this.x, this.x + this.w, x) && | |
between(this.y, this.y + this.h, y); | |
} | |
corners() { | |
return [[this.x, this.y], [this.x + this.w, this.y], [this.x, this.y + this.h], [this.x + this.w, this.y + this.h]]; | |
} | |
} | |
const between = (a: number, b: number, val: number) => b > a ? a <= val && val <= b : b <= val && val <= a; | |
export class Selector { | |
selection: Selection; | |
private targets: HTMLElement[] = []; | |
selectHandlers: Set<(els: HTMLElement[]) => void> = new Set; | |
private computedTargets: {el: HTMLElement, x: number, y: number, width: number, height: number}[]; | |
private iw: number; | |
private ih: number; | |
private prevScrollY: number; | |
constructor(selection: Selection) { | |
this.selection = selection; | |
addEventListener('scroll', ev => { | |
if (!drag.isPushed) return; | |
this.selection.resize(this.selection.w, this.selection.h + (scrollY - this.prevScrollY)); | |
console.log(this.selection) | |
this.prevScrollY = scrollY; | |
this.checkSelection(); | |
}); | |
downHandlers.add(({x, y}, {pageX, pageY}) => { | |
this.computedTargets = this.targets.map(el => { | |
const {x, y, width, height} = el.getBoundingClientRect(); | |
return {el, x: x + scrollX, y: y + scrollY, width, height}; | |
}) | |
this.selection.show(); | |
this.selection.draw(pageX, pageY, 0, 0); | |
this.prevScrollY = scrollY; | |
}); | |
moveHandlers.add(({x, y}, {pageX, pageY}) => { | |
this.selection.resize(pageX - this.selection.x, pageY - this.selection.y); | |
this.checkSelection(); | |
}); | |
upHandlers.add(() => { | |
this.selection.hide(); | |
this.checkSelection(); | |
}); | |
this.iw = innerWidth; | |
this.ih = innerHeight; | |
addEventListener('resize', () => { | |
this.iw = innerWidth; | |
this.ih = innerHeight; | |
}) | |
} | |
private checkSelection() { | |
const selectedElements = this.isElementsInSelection(); | |
this.selectHandlers.forEach(f => f(selectedElements.map(({el}) => el))); | |
} | |
isElementsInSelection() { // 두가지 옵션, 닿자마자, 모두 포함 | |
const els = this.computedTargets.filter(({el, x, y, width, height}) => { | |
const corners = [[x, y], [x + width, y], [x, y + height], [x + width, y + height]]; | |
return corners.some(([x, y]) => this.selection.isPointInSelection(x, y)); | |
}); | |
return els; | |
}; | |
clear() { | |
this.selectHandlers.clear(); | |
this.targets.splice(0); | |
} | |
listen(f) { | |
this.selectHandlers.add(f); | |
} | |
setTargets(els: HTMLElement[]) { | |
this.targets.push(...els); | |
} | |
}; | |
// const selection = new Selection; | |
// const selector = new Selector(selection); | |
// const spans = Array.from({length: 2000}, (_, i) => { | |
// const el = Object.assign(document.createElement('span'), {innerText: i + ' '}); | |
// document.body.appendChild(el); | |
// return el; | |
// }) | |
// selector.setTargets(spans); | |
// selector.listen(seleted => { | |
// spans.forEach(el => el.style.color = 'black'); | |
// seleted.forEach(el => el.style.color = 'red'); | |
// }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment