JSFiddle - React, Tailwind, and code Playground

by Scott Kaye

HTML

<div>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicin doloribus?</p>
    <p>Lorem ipsum dolor sit amet.</p>
    <p>Lorem ipsum dolor sit amet.</p>
</div>

SCSS

#kHighlighter {
	width: 100%;
	background: #eee;
	border-top: 1px solid #ccc;
	font-family: Segoe UI;
	font-size: 0.85em;
	position: fixed;
	bottom: 0;
	left: 0;
	
	.highlighter-thumb {
		-webkit-user-select: none;
		cursor: pointer;
		display: inline-block;
		width: 30px;
		text-align: right;
		box-shadow: inset 5px 0 0 rgba(0,0,0,0.1);
		margin: 0 2px;
		padding: 2px 5px;
		background: #ccc;
		color: #000;
		position: relative;

		&.selected {
			font-weight: 700;
			box-shadow: inset 5px 0 0 rgba(0,0,0,0.1), 0 -5px 0;
		}
	}
}

JavaScript

const $highlighter = Symbol("highlighter");
const $isHighlighter = Symbol("is-highlighter");

function getTreePath(el) {
	let parent = el;
	let path = [];
	let limit = 0;
	
	while(!parent.isSameNode(document.documentElement) && ++limit < 100) {
		path.unshift([...parent.parentNode.childNodes].indexOf(parent));
		parent = parent.parentNode;
	}
	
	return path;
}

let selectedHighlighter = null;
class kHighlighter {
	constructor(parent) {
		this.element = this.create(parent);     
		document.body.addEventListener("mouseup", kHighlighter.documentMouseUp, false);
		
		// Load saved highlights
		let saved = new Map(JSON.parse(localStorage["kHighlighter"] || "[]"));
		for (let [path, h] of saved) {
			path = JSON.parse(path);
			let el = document.documentElement;

			path.forEach(i => {
				if (!el || !el.childNodes) return;
				el = el.childNodes[i];
			});
			
			if (el) {
				new Highlighter(h.bg, h.fg).highlightNode(el, false);
			}
		}
	}

	handleSelect(el) {
		if (selectedHighlighter) {
			selectedHighlighter.classList.remove("selected");
		}

		if (selectedHighlighter === el) {
			el.classList.remove("selected");
			selectedHighlighter = null;
			this.exitHighlightMode();
			return;
		}

		selectedHighlighter = el;
		el.classList.add("selected");
		this.enterHighlightMode();
	}

	enterHighlightMode() {
		let style = document.querySelector("#kHighlighter-style") || document.createElement("style");
		style.innerText = `*::selection {
			background: ${selectedHighlighter[$highlighter].bg};
			color: ${selectedHighlighter[$highlighter].fg}
		}`;
		style.id = "kHighlighter-style";
		document.body.appendChild(style);       
	}
	
	exitHighlightMode() {
		document.body.removeChild(document.querySelector("#kHighlighter-style"));
		selectedHighlighter = null;
	}

	static documentMouseUp(e) {
		if (!selectedHighlighter) return;
	
		if (!e.target || e.target[$isHighlighter]) {
			return;
		}

		selectedHighlighter[$highlighter].highlightNode(e.target);
	}

	create(parent)...