Skip to content

Instantly share code, notes, and snippets.

@roseg43
Created January 20, 2023 16:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save roseg43/75bbb53217f58f249024e1a9e5bcbca0 to your computer and use it in GitHub Desktop.
Save roseg43/75bbb53217f58f249024e1a9e5bcbca0 to your computer and use it in GitHub Desktop.
Simple tool for selecting content on a page using a visible marquee
(() => {
/**
* Renders a button that when clicked copies the NodeList passed to the constructor to the clipboard
*
* @param [NodeList] content - a NodeList of selected elements
*/
const CopyToClipBoardBtn = (content) => {
const btn = document.createElement('button');
btn.innerText = 'Copy to clipboard';
btn.style.padding = '10px';
btn.style.border = 'none';
btn.style.borderRadius = '5px';
btn.style.backgroundColor = '#000';
btn.style.color = '#fff';
btn.style.cursor = 'pointer';
btn.style.marginTop = '10px';
btn.addEventListener('click', () => {
navigator.clipboard.writeText(content);
});
return btn;
};
/**
* Generates a dead simple modal using the supplied content
* @param content [NodeList] A list of elements to render in the modal
* @returns
*/
const Modal = (content) => {
const modal = document.createElement('div');
modal.classList.add('modal');
modal.style.position = 'fixed';
modal.style.width = '100%';
modal.style.height = '100%';
modal.style.backgroundColor = 'rgba(255,255,255,0.9)';
modal.style.top = 0;
modal.style.left = 0;
modal.style.display = 'grid';
modal.style.gridTemplateAreas = '"gutter-left modal-content gutter-right"';
modal.style.gridTemplateColumns = '1fr 10fr 1fr';
modal.style.justifyContent = 'center';
modal.style.alignItems = 'center';
const modalContent = document.createElement('div');
modalContent.classList.add('modal-content');
modalContent.style.gridArea = 'modal-content';
const modalTitle = document.createElement('h1');
modalTitle.innerText = 'Selected Content';
modalContent.appendChild(modalTitle);
// Add the content to the modal
modalContent.append(...content);
// Add a copy to clipboard button
const copyBtn = CopyToClipBoardBtn(content);
modalContent.appendChild(copyBtn);
modal.append(modalContent);
const open = () => {
document.body.appendChild(modal);
// Prevent scroll on the html root
document.documentElement.style.overflow = 'hidden';
};
const close = () => {
modal.remove();
// Re-enable scroll on the html root
document.documentElement.style.overflow = null;
};
// If the user presses escape, close the modal
const onEscape = (e) => {
// Check if the modal has been rendered
if (!document.querySelector('.modal')) return;
if (e.key === 'Escape') {
close();
}
};
document.addEventListener('keydown', onEscape);
return {
open,
close
};
}
/**
* When initialized, registers event listeners to allow the
* user to click and drag to select a rectangular area of the screen.
* The selected area is then used to select all elements within it.
*
*/
const SelectionMarquee = () => {
const init = () => {
console.log('init');
let isMouseDown = false;
let startX = 0;
let startY = 0;
let marquee = null;
const onMouseDown = (e) => {
console.log('mousedown')
// Makse sure shift is pressed
if (!e.shiftKey) return;
isMouseDown = true;
console.log(e);
startX = e.clientX;
startY = e.layerY;
marquee = document.createElement('div');
marquee.classList.add('marquee');
marquee.style.left = `${startX}px`;
marquee.style.top = `${startY}px`;
marquee.style.backgroundColor = 'red';
marquee.style.position = 'absolute';
marquee.style.opacity = '0.4';
document.body.appendChild(marquee);
};
const onMouseMove = (e) => {
console.log('move');
if (!isMouseDown) return;
const width = e.clientX - startX;
const height = e.layerY - startY;
marquee.style.width = `${width}px`;
marquee.style.height = `${height}px`;
};
const onMouseUp = (e) => {
if (!isMouseDown) return;
isMouseDown = false;
const marqueeRect = marquee.getBoundingClientRect();
const elements = document.querySelectorAll('*');
for (let i = 0; i < elements.length; i++) {
const elementRect = elements[i].getBoundingClientRect();
if (isIntersecting(marqueeRect, elementRect)) {
elements[i].classList.add('selected');
}
}
//marquee.remove();
// Get the selected elements and render them into a modal
const selectedEls = getSelectedElements();
const modal = Modal(selectedEls);
modal.open();
clearSelectedEls();
};
/**
* Gets all selected elements that can be considered content
* @returns {NodeList} A list of all elements that have the class 'selected'
*/
const getSelectedElements = () => {
const allowedTags = [
'P',
'H1',
'H2',
'H3',
'H4',
'H5',
'H6',
'UL',
'OL',
'LI',
'A',
'IMG',
'FIGURE',
'FIGCAPTION',
'BLOCKQUOTE',
'PRE',
'CODE',
'TABLE',
'THEAD',
'TBODY',
'TR',
'TH',
'TD',
'CAPTION',
'EM',
'STRONG'
];
// Get all selected elements whose tagname is in the allowedTags array
const selectedEls = Array.from(document.querySelectorAll('.selected'));
return selectedEls.filter((el) => allowedTags.includes(el.tagName));
};
const isIntersecting = (rectA, rectB) => {
return !(
rectA.right < rectB.left ||
rectA.left > rectB.right ||
rectA.bottom < rectB.top ||
rectA.top > rectB.bottom
);
};
const clearSelectedEls = () => {
const selectedEls = document.querySelectorAll('.selected');
selectedEls.forEach((el) => el.classList.remove('selected'));
};
const clearMarquee = () => {
if (marquee) marquee.remove();
};
document.addEventListener('mousedown', onMouseDown);
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
}
init();
}
SelectionMarquee();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment