Skip to content

Instantly share code, notes, and snippets.

@fronterior
Created February 25, 2022 22:40
Show Gist options
  • Save fronterior/54330e65d1830c0965541220764a5817 to your computer and use it in GitHub Desktop.
Save fronterior/54330e65d1830c0965541220764a5817 to your computer and use it in GitHub Desktop.
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