Skip to content

Instantly share code, notes, and snippets.

@carlvlewis
Last active November 22, 2016 12:54
Show Gist options
  • Save carlvlewis/7e923231a694e7af1a229d69b9f5b59b to your computer and use it in GitHub Desktop.
Save carlvlewis/7e923231a694e7af1a229d69b9f5b59b to your computer and use it in GitHub Desktop.
Text highlighter
<article>
<header>
<h1>Text highlighter</h1>
</header>
<div class="content">
<p>
<span class="dropcap">At</span> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce aliquam diam
id
sapien convallis
ornare.
Phasellus hendrerit aliquam dolor, ac mattis augue iaculis in. Integer tincidunt et justo in vulputate.
Aliquam ac venenatis ligula. Quisque luctus dignissim justo non ornare. Vivamus at metus justo. Nullam
condimentum aliquet rhoncus. Morbi facilisis lectus a maximus maximus. Fusce nec vehicula nisi. Mauris
mollis tempus efficitur. Fusce leo arcu, tristique at ex vel, viverra feugiat arcu. Vivamus dictum magna non
pharetra laoreet. Praesent at magna turpis. Suspendisse elementum commodo condimentum.
</p>
<p>
Fusce sit amet aliquam risus, ut ultrices nunc. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Mauris
elit mauris, pharetra vel mauris in, consequat tincidunt tortor. Fusce suscipit risus vitae congue mattis.
In
placerat eu lorem at efficitur. Cras facilisis diam vel metus viverra tristique. In sagittis metus non augue
tincidunt tristique. Mauris egestas eget odio et scelerisque. Phasellus porttitor sollicitudin velit vitae
mattis. Praesent quam mi, volutpat vitae dignissim vel, tempor vitae sem. Duis non laoreet quam, quis
feugiat
libero. Integer scelerisque ultricies velit id interdum. Maecenas mollis tortor eu magna tristique, a
suscipit
risus posuere. Integer ut sagittis magna. Nullam fermentum ullamcorper ex eu laoreet.
</p>
<p>Sed fermentum augue justo, fringilla semper nisl sagittis a. Mauris sed felis felis. Mauris vel nisl mollis,
maximus eros nec, bibendum arcu. Aenean tempus, tortor sed cursus luctus, ex justo luctus libero, et
ullamcorper
ex turpis non nibh. Ut vel massa ac lorem fringilla rhoncus ut a sem. Aliquam erat volutpat. Phasellus nec
aliquam libero. Suspendisse vel finibus ligula. Nullam magna metus, posuere vel est sed, aliquam malesuada
felis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aenean
non
aliquam nisi. Morbi tellus est, viverra ac finibus eget, viverra nec justo. Ut nisl lorem, tincidunt nec
eros
non, fringilla gravida sapien. Nam sed arcu ac lorem dignissim sagittis. Aliquam blandit, nibh id pulvinar
vehicula, velit justo viverra arcu, ut sagittis eros ante non elit. Nulla tellus ipsum, congue malesuada
gravida
vel, consectetur non massa.
</p>
<p>
Integer nulla leo, consequat sed arcu ac, pellentesque tempus tortor. Fusce interdum est purus, eu venenatis
sem
porttitor ut. Nullam vestibulum et purus quis ultricies. Quisque pulvinar maximus metus ut sodales. Proin
eget
rutrum nisi. Suspendisse vulputate neque sit amet erat sagittis, in faucibus tortor ornare. Quisque
efficitur
posuere pulvinar. Nam sollicitudin venenatis lacinia. Fusce condimentum tristique tortor ac sollicitudin.
Nullam
orci leo, pretium non sodales sed, tempus non lectus. Nullam sollicitudin lacus non aliquam pretium. Cras
leo
velit, gravida quis congue at, sodales in dolor. Etiam tincidunt nulla et neque commodo iaculis. Nunc quam
justo, aliquam id sollicitudin vitae, blandit nec enim. Phasellus vel consequat ipsum.</p>
<p>
Aliquam accumsan turpis quam, a tempor diam egestas in. Maecenas dapibus ultrices malesuada. Phasellus
interdum
ante a ligula imperdiet pellentesque. Cras id augue sagittis, malesuada metus nec, eleifend mauris. Mauris
vitae
augue facilisis, ultricies nunc id, lobortis purus. Curabitur nisl purus, placerat ac lacinia sed, vulputate
in
lacus. Mauris at tellus est. Sed sed ipsum in felis vulputate elementum et eget odio. Donec ac dictum diam,
fermentum accumsan nulla. Aenean eget interdum massa, ac congue libero. Donec dapibus vehicula ex, tincidunt
tincidunt purus tempus nec. Praesent metus leo, facilisis sed felis pellentesque, lacinia tincidunt magna.
</p>
<p>
Fusce sit amet hendrerit mi. Sed quis libero at magna facilisis ultricies. Curabitur eget sem et diam
condimentum elementum. Suspendisse elementum sollicitudin augue ac ornare. Nulla eu laoreet dui. Proin nulla
urna, interdum vitae ultricies ac, laoreet in augue. Fusce aliquet aliquet mauris, sit amet malesuada nisl
tempor quis. Duis non ornare mi. Praesent sollicitudin sodales nibh, non malesuada ligula vehicula ut.
</p>
<p>Aliquam non congue nibh. Donec eget magna ante. In bibendum mauris sit amet justo porta consectetur.
Proin et
est vel quam suscipit dictum. Aliquam sed sem sem. Aliquam ac augue sollicitudin, finibus quam in, maximus
erat.
Sed mattis, mi a sollicitudin imperdiet, mauris enim semper metus, viverra mattis risus ex nec tellus.
Phasellus
purus ligula, volutpat vitae sapien eu, molestie rutrum nulla. Quisque dapibus id risus et maximus. Vivamus
sollicitudin dui ut sem maximus, ac pharetra leo accumsan. Quisque efficitur turpis eget vulputate lacinia.
</p>
</div>
</article>
/*
I've always liked Medium's approach to highlighting text in articles and making notes, but I never quite knew how it works, so I experimented a bit in my spare time.
This implementation is not production ready, it has only been tested on latest FF (some noticable bugs) and Chrome (seems to work best in chrome).
As said, this was just an exploration into trying to remake Medium's solution and explore the options.
I hope you like it, fork it and hopefully some day use it in production ;)
btw, I picked up the design inspiration from
https://uimovement.com/ui/304/text-editor-animation/
*/
const defaultOptions = {
mods: ['hash', 'italic', 'bold', 'color']
};
// Not passing options.selector will result in highlighter binding to the entire document
class highlighter {
constructor(options) {
// Merge options
const opts = Object.assign({}, defaultOptions, options);
this.options = opts;
this.IS_OPEN = false;
this.OPENMOD = null;
// Current active range
this.activeRange;
// Span wrapper
this.wrapperNode;
this.colorCircleNode;
this.colorInputNode;
this.hashInputNode;
this.DEFAULT_STATE = {
bold: false,
italic: false,
color: '#000000',
hash: []
};
this.STATE = {
bold: false,
italic: false,
color: '#000000',
hash: []
};
this.render(this.options.mods);
this.preventHighlighterPropagation(this.highlighterNode);
this.bindModListeners(this.options.mods);
this.bindListeners(this.options.selector);
}
setState(state) {
this.colorCircleNode.style.color = state.color;
this.colorInputNode.style.value = state.color;
this.hashInputNode.value = state.hash.join('');
}
preventHighlighterPropagation(hg) {
hg.addEventListener('mousedown', (event) => {
event.stopPropagation();
});
hg.addEventListener('mouseup', (event) => {
event.stopPropagation();
});
}
getSelectionText() {
let text = "";
let selection;
if (window.getSelection) {
selection = window.getSelection();
text = selection.toString();
}
return {
text,
selection
};
}
getSelectionPosition(selection) {
const range = selection.getRangeAt(0);
this.activeRange = range;
const position = range.getBoundingClientRect();
const topScroll = window.pageYOffset || document.documentElement.scrollTop;
const leftScroll = window.pageXOffset || document.documentElement.scrollLeft;
return {
start: range.startOffset,
end: range.endOffset,
x: position.left + position.width / 2 + leftScroll - 62.5,
y: position.top + topScroll - 60
}
}
createSpanWrapper() {
const span = document.createElement('span');
span.className = "hgspan hgspan--selected";
span.addEventListener('click', (event) => {
this.showHighlighter(event, true)
});
this.wrapperNode = span;
return span;
}
showHighlighter(event, fromExistingNode) {
const {
text,
selection
} = this.getSelectionText();
const {
x,
y
} = this.getSelectionPosition(selection);
// Exit if range has no selection
if (this.activeRange.collapsed && !fromExistingNode) {
return false;
}
this.highlighterNode = this.highlighterNode || document.getElementById('hg');
this.selectionNode = event.target;
this.STATE = JSON.parse(this.selectionNode.getAttribute('data-hg')) || Object.assign({}, this.DEFAULT_STATE);
if (this.selectionNode.className !== "hgspan") {
const newspan = this.createSpanWrapper();
const text = this.activeRange.extractContents();
newspan.appendChild(text);
this.activeRange.insertNode(newspan);
} else {
this.wrapperNode = this.selectionNode;
}
if (fromExistingNode) {
this.setState(this.STATE);
this.activeRange.selectNode(event.target);
this.selectionNode.className = 'hgspan hgspan--selected'
}
this.highlighterNode.classList.add('hg-wrp--open');
this.highlighterNode.setAttribute('aria-hidden', false);
this.highlighterNode.style.cssText = `left:${x}px;top:${y}px`;
}
hideHighlighter(event) {
const isEqual = isEqualObject(this.STATE, this.DEFAULT_STATE);
this.highlighterNode = this.highlighterNode || document.getElementById('hg');
if (isEqual && !this.activeRange.collapsed) {
this.activeRange.deleteContents();
this.activeRange.insertNode(document.createTextNode(this.wrapperNode.innerHTML));
} else {
this.wrapperNode.className = "hgspan";
this.wrapperNode.setAttribute('data-hg', JSON.stringify(this.STATE));
}
this.highlighterNode.setAttribute('aria-hidden', true);
this.highlighterNode.className = "hg-wrp";
this.setState(this.DEFAULT_STATE);
this.OPENMOD = null;
}
bindListeners(selector) {
if (selector) {
const nodes = document.querySelectorAll(selector);
if (!nodes) {
throw new Error('Selector doesnt match any elements');
}
nodes.forEach((node) => {
node.addEventListener('mouseup', (event) => {
this.showHighlighter(event);
});
});
} else {
document.addEventListener('mouseup', (event) => {
this.showHighlighter(event);
});
}
document.addEventListener('mousedown', (event) => {
this.hideHighlighter(event);
});
}
bindModListeners(mods) {
if (mods.indexOf('color') > -1) {
this.bindColorMod();
}
if (mods.indexOf('hash') > -1) {
this.bindHashMod();
}
if (mods.indexOf('bold') > -1) {
this.bindBoldMod();
}
if (mods.indexOf('italic') > -1) {
this.bindItalicMod();
}
}
setSelection() {
const range = document.createRange();
range.selectNodeContents(this.selectionNode);
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
bindColorMod() {
this.highlighterNode = this.highlighterNode || document.getElementById('hg');
this.colorInputNode = this.colorInputNode || this.highlighterNode.querySelector('input');
this.colorCircleNode = this.colorCircleNode || this.highlighterNode.querySelector('.hg__option--color' +
' .hg-option--open');
const openButton = this.highlighterNode.querySelector('.hg-option--open'),
closeButton = this.highlighterNode.querySelector('.hg-option--close');
openButton.addEventListener('click', (event) => {
this.openColor(event, this.wrapperNode);
});
closeButton.addEventListener('click', this.closeColor);
};
bindHashMod() {
this.highlighterNode = this.highlighterNode || document.getElementById('hg');
this.hashInputNode = this.hashInputNode || this.highlighterNode.querySelector('.hg-option--hash input');
const openButton = this.highlighterNode.querySelector('.hg-option--openhash');
openButton.addEventListener('click', (event) => {
this.openHashMod(event);
});
this.hashInputNode.addEventListener('input', (event) => {
this.setHash(event);
});
}
bindBoldMod() {
this.highlighterNode = this.highlighterNode || document.getElementById('hg');
const boldButton = this.highlighterNode.querySelector('.hg-option--togglebold');
boldButton.addEventListener('click', (event) => {
this.addBold();
});
}
addBold() {
this.STATE.bold = !this.STATE.bold;
this.wrapperNode.style.fontWeight = this.STATE.bold ? "bold" : null;
}
bindItalicMod() {
this.highlighterNode = this.highlighterNode || document.getElementById('hg');
const boldButton = this.highlighterNode.querySelector('.hg-option--toggleitalic');
boldButton.addEventListener('click', (event) => {
this.addItalic();
});
}
addItalic() {
this.STATE.italic = !this.STATE.italic;
this.wrapperNode.style.fontStyle = this.STATE.italic ? "italic" : null;
}
openHashMod(event) {
this.highlighterNode = this.highlighterNode || document.getElementById('hg');
if (this.OPENMOD === "HASH") {
this.highlighterNode.classList.remove('hg-option--hash');
this.highlighterNode.className = "hg-wrp hg-wrp--open hg-wrp--closefromhash";
setTimeout(() => {
hg.className = "hg-wrp hg-wrp--open";
}, 500);
this.OPENMOD = null;
} else {
this.highlighterNode.classList.add('hg-option--hash');
this.OPENMOD = "HASH";
}
}
setHash(event) {
this.STATE.hash = event.target.value.replace(/\s+/, "").split(',');
}
openColor(event) {
this.OPENMOD = "COLOR";
this.highlighterNode = this.highlighterNode || document.getElementById('hg');
this.highlighterNode.classList.add('hg-option--color');
this.colorInputNode.addEventListener('input', (event) => {
this.changeColor(event);
});
}
changeColor(event, circle) {
const HexRex = /^#[0-9A-Fa-f]{6}/;
const color = event.target.value;
if (HexRex.test(color)) {
this.colorCircleNode.style.color = color;
this.wrapperNode.style.color = color;
this.STATE.color = color;
}
}
closeColor(event) {
this.highlighterNode = this.highlighterNode || document.getElementById('hg');
this.OPENMOD = null;
this.highlighterNode.className = "hg-wrp hg-wrp--open hg-wrp--closefromcolor";
setTimeout(() => {
this.highlighterNode.className = "hg-wrp hg-wrp--open";
}, 500);
}
render(mods) {
const template = `
<div class="hg-options">
${mods.indexOf('color') > -1 ? `<div id="hgcolor" class="hg__option hg__option--color">
<button class="hg-option--close hg-options">OK</button>
<button class="hg-option hg-option--open">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
id="Capa_1" x="0px" y="0px" width="30px" height="30px" viewBox="0 0 30 30"
style="enable-background:new 0 0 15 15;" xml:space="preserve">
<circle cx="12" cy="15" r="12" fill="currentColor"></circle>
</svg>
</button>
<input type="text" value="#000000">
</div>` : ""}
${mods.indexOf('italic') > -1 ? `<div id="hgitalic" class="hg__option hg-option--italic">
<button class="hg-option hg-option--toggleitalic">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="994.4px" height="994.4px" viewBox="0 0 994.4 994.4" style="enable-background:new 0 0 994.4 994.4;" xml:space="preserve">
<path fill="currentColor"
d="M524.65,766.3c2.3-7.699,4.6-15.6,6.6-23.6c0.301-1.3,35.101-135.6,72.301-275.8c64-241.1,79.6-289.6,83.1-297 c11.9-24.6,31.2-43.2,59-56.9c24-11.8,54.6-19.6,91-23.2c10.7-1.1,20.8-7,28.2-16.6c6.7-8.7,10.6-19.7,10.6-30.2 c0-23.7-19.3-43-43-43h-487.5c-23.7,0-43,19.3-43,43v1.5c0,21.2,15.2,39,36.101,42.4c27.699,4.5,65.699,10.7,95.5,24.8 c15.3,7.2,27.199,16,35.1,26c9,11.3,13.4,24.4,13.4,39.9c0,29-8.5,58.1-16.7,86.2c-1.7,5.9-3.5,12.1-5.2,18.1 c-10.5,37.5-26,94.4-43.9,160.3c-41.3,151.7-92.699,340.4-105.6,376.3c-8.7,24.2-41,81.5-152.1,90c-10.8,0.8-20.9,5.7-28.2,13.601 c-7.4,8-11.4,18.3-11.4,29.199v0.101c0,23.7,19.3,43,43,43h487.601c23.699,0,43-19.3,43-43v-0.3c0-21.7-16.2-40-37.7-42.601 c-4.2-0.5-8.3-1-12.7-1.5c-42.8-4.899-96-11.1-118.4-40.2c-10.399-13.5-13.699-31.3-10.3-54.199 C515.65,797.101,520.051,782.101,524.65,766.3z"/>
</svg>
</button>
</div>` : ""}
${mods.indexOf('bold') > -1 ? `<div id="hgbold" class="hg__option hg-option--bold">
<button class="hg-option hg-option--togglebold">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="438.543px" height="438.543px" viewBox="0 0 438.543 438.543" style="enable-background:new 0 0 438.543 438.543;" xml:space="preserve">
<path fill="currentColor"
d="M394.005,235.541c-17.131-19.987-42.537-33.878-76.231-41.683c23.612-10.85,37.887-18.177,42.835-21.982 c11.991-8.947,21.032-18.942,27.113-29.98c6.092-11.042,9.134-23.223,9.134-36.545c0-12.371-1.995-23.981-5.995-34.831 c-3.997-10.852-10.182-20.749-18.556-29.694c-8.565-8.943-18.273-16.18-29.122-21.7c-10.089-4.947-19.226-8.658-27.411-11.132 c-19.603-5.14-37.781-7.71-54.529-7.71h-21.128c-3.806,0-7.666-0.048-11.567-0.144C224.65,0.05,222.604,0,222.414,0 c-0.949,0-2.284,0.05-4.002,0.141c-1.713,0.096-3.049,0.144-3.999,0.144l-12.85,0.287L93.074,4.283l-74.23,1.714l1.142,23.695 c15.986,2.096,26.84,3.337,32.548,3.715c9.707,0.571,16.274,2.002,19.701,4.283c2.096,1.525,3.238,2.666,3.428,3.427 c1.902,4.187,2.946,14.56,3.14,31.121c0.76,28.171,1.619,66.619,2.568,115.344l0.571,141.896c0,24.359-0.855,42.828-2.568,55.388 c-0.761,4.569-2.762,9.422-5.997,14.562c-8.756,3.614-20.461,6.567-35.117,8.85c-4.375,0.568-10.848,1.711-19.412,3.43 l-0.571,26.836c45.489-1.526,71.374-2.57,77.663-3.142c40.729-2.478,69.093-3.521,85.08-3.142l56.242,1.137 c22.087,0.76,40.929-0.288,56.534-3.139c24.742-4.568,44.057-10.283,57.958-17.135c14.082-6.851,27.404-17.131,39.964-30.833 c9.527-10.466,16.275-21.601,20.272-33.407c5.521-16.174,8.278-31.494,8.278-45.963 C420.273,278.181,411.52,255.718,394.005,235.541z M170.456,33.126c14.846-2.474,27.218-3.711,37.115-3.711 c32.546,0,56.82,7.139,72.805,21.413c16.169,14.272,24.263,32.071,24.263,53.387c0,30.266-8.467,51.583-25.406,63.954 c-16.939,12.37-42.065,18.558-75.373,18.558c-12.562,0-22.935-0.665-31.118-1.997c-0.193-6.473-0.288-13.8-0.288-21.986 l0.288-27.979c0.188-29.88-0.383-56.431-1.714-79.656C170.646,48.829,170.456,41.506,170.456,33.126z M313.211,362.879 c-7.043,13.702-18.657,24.458-34.83,32.265c-16.181,7.806-36.74,11.703-61.671,11.703c-12.182,0-25.506-3.038-39.971-9.13 c-2.284-5.517-3.427-9.712-3.431-12.566l-0.854-77.088l0.288-49.392v-41.114c5.14-1.903,14.753-2.853,28.837-2.853 c31.787,0,55.291,3.046,70.519,9.135c15.797,6.096,29.218,18.086,40.258,35.978c7.803,12.566,11.704,29.694,11.704,51.394 C324.056,332.333,320.438,349.557,313.211,362.879z"/>
</svg>
</button>
</div>` : ""}
${mods.indexOf('hash') > -1 ? `<div id="hghash" class="hg__option hg-option--hash">
<button class="hg-option hg-option--openhash">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="457.03px" height="457.03px" viewBox="0 0 457.03 457.03" style="enable-background:new 0 0 457.03 457.03;" xml:space="preserve">
<path fill="currentColor"
d="M421.512,207.074l-85.795,85.767c-47.352,47.38-124.169,47.38-171.529,0c-7.46-7.439-13.296-15.821-18.421-24.465 l39.864-39.861c1.895-1.911,4.235-3.006,6.471-4.296c2.756,9.416,7.567,18.33,14.972,25.736c23.648,23.667,62.128,23.634,85.762,0 l85.768-85.765c23.666-23.664,23.666-62.135,0-85.781c-23.635-23.646-62.105-23.646-85.768,0l-30.499,30.532 c-24.75-9.637-51.415-12.228-77.373-8.424l64.991-64.989c47.38-47.371,124.177-47.371,171.557,0 C468.869,82.897,468.869,159.706,421.512,207.074z M194.708,348.104l-30.521,30.532c-23.646,23.634-62.128,23.634-85.778,0 c-23.648-23.667-23.648-62.138,0-85.795l85.778-85.767c23.665-23.662,62.121-23.662,85.767,0 c7.388,7.39,12.204,16.302,14.986,25.706c2.249-1.307,4.56-2.369,6.454-4.266l39.861-39.845 c-5.092-8.678-10.958-17.03-18.421-24.477c-47.348-47.371-124.172-47.371-171.543,0L35.526,249.96 c-47.366,47.385-47.366,124.172,0,171.553c47.371,47.356,124.177,47.356,171.547,0l65.008-65.003 C246.109,360.336,219.437,357.723,194.708,348.104z"/>
</svg>
</button>
<input type="text" placeholder="Work, school">
</div>` : ""}
</div>`;
const container = document.createElement('div');
container.className = "hg-wrp";
container.id = "hg";
container.innerHTML = template;
container.setAttribute('aria-hidden',true);
document.body.appendChild(container);
this.highlighterNode = document.getElementById('hg');
}
}
// utils
function isEqualObject(obj1 = {}, obj2 = {}) {
let matches = true;
Object.keys(obj1).forEach(key => {
if (obj1.hasOwnProperty(key)) {
if (typeof obj1[key] === "boolean" || typeof obj1[key] === "string") {
if (obj1[key] !== obj2[key]) {
matches = false;
}
} else if (obj1[key] instanceof Array) {
if (obj1[key].length !== obj2[key].length) {
matches = false;
}
}
}
});
return matches;
}
const hg = new highlighter({
selector:'.content p'
});
@import url('https://fonts.googleapis.com/css?family=Abhaya+Libre');
@import url('https://fonts.googleapis.com/css?family=Roboto+Condensed:700');
* {
box-sizing: border-box;
}
// highlighter css
.hgspan {
cursor: pointer;
background: linear-gradient(to bottom, rgba(173, 255, 207, 1), rgba(173, 255, 207, 1));
}
.hg-wrp {
position: absolute;
width: 135px;
height: 40px;
background-color: #fff;
box-shadow: 0 0 10px 2px rgba(0, 0, 0, .2);
border-radius: 25px;
transform-origin: center bottom;
overflow: hidden;
z-index: 1;
opacity: 0;
margin-bottom: 1em;
// initial state!
transition: opacity 130ms linear, transform 130ms linear;
transform: translateY(30px) scaleX(.12) scaleY(.3);
will-change: transform;
// Open state
&--open {
opacity: 1;
transition: opacity 320ms 60ms cubic-bezier(0.23, 1, 0.32, 1),
transform 550ms cubic-bezier(0.23, 1, 0.32, 1);
transform: translate(0, 15px) scaleX(1) scaleY(1);
}
svg {
max-height: 20px;
width: 100%;
}
.hg-options {
padding: 8px;
display: flex;
align-items: center;
justify-content: center;
.hg-option {
-webkit-appearance: none;
border: none;
background-color: #fff;
cursor: pointer;
position: relative;
z-index: 2;
&:focus {
outline: none;
}
}
.hg__option {
max-width: 25%;
&.hg-option--hash {
input {
position: absolute;
width: 100%;
height: 40px;
line-height: 40px;
padding: 8px 34px 8px 24px;
border: none;
left: 0;
top: 0;
background: transparent;
font-size: 1.1rem;
visibility: hidden;
opacity: 0;
&:focus {
outline: none;
}
}
}
&--color {
.hg-option--open {
z-index: 2;
position: relative;
cursor: pointer;
background-color: transparent;
}
.hg-option--close {
padding: 0;
z-index: 2;
color: white;
background-color: #309eeb;
position: absolute;
border-radius: 100%;
left: 6px;
top: 50%;
transform: translate(0, -50%) scale(.3);
width: 32px;
height: 32px;
line-height: 16px;
border: none;
-webkit-appearance: none;
cursor: pointer;
&:focus {
outline: none;
}
}
input {
position: absolute;
width: 100%;
height: 40px;
line-height: 40px;
padding: 8px 34px 8px 34px;
border: none;
left: 0;
top: 0;
background: transparent;
font-size: 1.1rem;
visibility: hidden;
opacity: 0;
&:focus {
outline: none;
}
}
}
}
}
// if Open!
@keyframes showBlueOk {
0% {
transform: translate(0, -50%) scale(.3);
}
100% {
transform: translate(92px, -50%) scale(1);
}
}
@keyframes hideBlueOk {
0% {
transform: translate(92px, -50%) scale(1);
}
20% {
transform: translate(92px, -50%) scale(1.15);
}
40% {
opacity: 1;
transform: translate(92px, -50%) scale(.95);
}
100% {
opacity: 0;
transform: translate(0px, -50%) scale(.3);
}
}
/**
** OPEN COLOR MOD
**/
&--open.hg-option--color {
$d: 70ms;
$trans: 170ms;
$ease: linear;
.hg-options {
.hg-option--close {
animation: showBlueOk 320ms 70ms cubic-bezier(.55, 0, .1, 1) forwards;
}
input {
transition: all 170ms $d * 1.88 linear;
z-index: 1;
opacity: 1;
visibility: visible;
}
}
.hg__option:nth-child(2) {
opacity: 0;
transition: opacity $trans $ease;
}
.hg__option:nth-child(3) {
opacity: 0;
transition: opacity $trans $d * 1.44 $ease;
}
.hg__option:nth-child(4) {
opacity: 0;
transition: opacity $trans $d * 1.88 $ease;
}
}
/**
** CLOSE COLOR MOD
**/
&--open.hg-wrp--closefromcolor {
$transout: 200ms;
$ease: linear;
$tt: 155ms;
$d: 80ms;
.hg-options {
input {
transition: all 170ms linear;
opacity: 0;
visibility: hidden;
}
.hg-option--close {
animation: hideBlueOk 350ms cubic-bezier(.55, 0, .1, 1) forwards;
}
}
.hg__option:nth-child(2) {
opacity: 1;
transition: opacity $transout $tt + $d * 3 $ease;
}
.hg__option:nth-child(3) {
opacity: 1;
transition: opacity $transout $tt + $d * 2 $ease;
}
.hg__option:nth-child(4) {
opacity: 1;
transition: opacity $transout $tt + $d * 1 $ease;
}
}
/**
** SHOW HASH MOD
**/
&--open.hg-option--hash {
$d: 70ms;
$trans: 170ms;
$ease: linear;
.hg-options {
.hg__option.hg-option--hash {
input {
z-index: 1;
opacity: 1;
transition: all 170ms $d * 1.88 linear;
visibility: visible;
}
}
}
.hg__option:nth-child(3) {
opacity: 0;
transition: opacity $trans $ease;
}
.hg__option:nth-child(2) {
opacity: 0;
transition: opacity $trans $d * 1.44 $ease;
}
.hg__option:nth-child(1) {
opacity: 0;
transition: opacity $trans $d * 1.88 $ease;
}
}
/**
** CLOSE HASH MOD
**/
&--open.hg-wrp--closefromhash {
$d: 70ms;
$trans: 170ms;
$ease: linear;
.hg-options {
.hg__option.hg-option--hash {
input {
opacity: 1;
transition: all 170ms linear;
visibility: hidden;
}
}
}
.hg__option:nth-child(1) {
opacity: 1;
transition: opacity $trans * 1.44 $ease;
}
.hg__option:nth-child(2) {
opacity: 1;
transition: opacity $trans $d * 1.88 $ease;
}
.hg__option:nth-child(3) {
opacity: 1;
transition: opacity $trans $d * 2.32 $ease;
}
}
}
// end highlighter css
html {
font-size: 62.5%;
font-family: 'Abhaya Libre', serif;
}
body {
margin: 0;
padding: 0;
}
header {
background-image: url(https://cdn-images-1.medium.com/max/2000/1*Hu3vQJOIihS2IKbyjgnhFg.png);
background-size: cover;
background-position: center;
padding: 120px 20px;
position: relative;
&:before {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
content: "";
background-color: rgba(0, 0, 0, .6);
}
h1 {
position: relative;
margin: 0;
text-align: center;
font-size: 6rem;
color: white;
text-transform: uppercase;
font-weight: 700;
font-family: 'Roboto Condensed', sans-serif
}
}
.content {
max-width: 700px;
margin: 0 auto;
p {
font-weight: 400;
font-style: normal;
font-size: 2.1rem;
line-height: 1.58;
letter-spacing: -.003em;
color: rgba(0, 0, 0, .8);
margin-top:50px;
.dropcap {
position: relative;
float: left;
font-weight: 700;
font-style: normal;
font-size: 7.2rem;
padding-top: 7px;
margin-left: -5px;
margin-right: 10px;
letter-spacing: -.03em;
line-height: .83;
margin-bottom: -.08em;
}
//&::selection{
// background-color: rgba(0,0,0,.8);
// color:white;
//}
}
}
<article>
<header>
<h1>Text highlighter</h1>
</header>
<div class="content">
<p>
<span class="dropcap">At</span> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce aliquam diam
id
sapien convallis
ornare.
Phasellus hendrerit aliquam dolor, ac mattis augue iaculis in. Integer tincidunt et justo in vulputate.
Aliquam ac venenatis ligula. Quisque luctus dignissim justo non ornare. Vivamus at metus justo. Nullam
condimentum aliquet rhoncus. Morbi facilisis lectus a maximus maximus. Fusce nec vehicula nisi. Mauris
mollis tempus efficitur. Fusce leo arcu, tristique at ex vel, viverra feugiat arcu. Vivamus dictum magna non
pharetra laoreet. Praesent at magna turpis. Suspendisse elementum commodo condimentum.
</p>
<p>
Fusce sit amet aliquam risus, ut ultrices nunc. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Mauris
elit mauris, pharetra vel mauris in, consequat tincidunt tortor. Fusce suscipit risus vitae congue mattis.
In
placerat eu lorem at efficitur. Cras facilisis diam vel metus viverra tristique. In sagittis metus non augue
tincidunt tristique. Mauris egestas eget odio et scelerisque. Phasellus porttitor sollicitudin velit vitae
mattis. Praesent quam mi, volutpat vitae dignissim vel, tempor vitae sem. Duis non laoreet quam, quis
feugiat
libero. Integer scelerisque ultricies velit id interdum. Maecenas mollis tortor eu magna tristique, a
suscipit
risus posuere. Integer ut sagittis magna. Nullam fermentum ullamcorper ex eu laoreet.
</p>
<p>Sed fermentum augue justo, fringilla semper nisl sagittis a. Mauris sed felis felis. Mauris vel nisl mollis,
maximus eros nec, bibendum arcu. Aenean tempus, tortor sed cursus luctus, ex justo luctus libero, et
ullamcorper
ex turpis non nibh. Ut vel massa ac lorem fringilla rhoncus ut a sem. Aliquam erat volutpat. Phasellus nec
aliquam libero. Suspendisse vel finibus ligula. Nullam magna metus, posuere vel est sed, aliquam malesuada
felis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aenean
non
aliquam nisi. Morbi tellus est, viverra ac finibus eget, viverra nec justo. Ut nisl lorem, tincidunt nec
eros
non, fringilla gravida sapien. Nam sed arcu ac lorem dignissim sagittis. Aliquam blandit, nibh id pulvinar
vehicula, velit justo viverra arcu, ut sagittis eros ante non elit. Nulla tellus ipsum, congue malesuada
gravida
vel, consectetur non massa.
</p>
<p>
Integer nulla leo, consequat sed arcu ac, pellentesque tempus tortor. Fusce interdum est purus, eu venenatis
sem
porttitor ut. Nullam vestibulum et purus quis ultricies. Quisque pulvinar maximus metus ut sodales. Proin
eget
rutrum nisi. Suspendisse vulputate neque sit amet erat sagittis, in faucibus tortor ornare. Quisque
efficitur
posuere pulvinar. Nam sollicitudin venenatis lacinia. Fusce condimentum tristique tortor ac sollicitudin.
Nullam
orci leo, pretium non sodales sed, tempus non lectus. Nullam sollicitudin lacus non aliquam pretium. Cras
leo
velit, gravida quis congue at, sodales in dolor. Etiam tincidunt nulla et neque commodo iaculis. Nunc quam
justo, aliquam id sollicitudin vitae, blandit nec enim. Phasellus vel consequat ipsum.</p>
<p>
Aliquam accumsan turpis quam, a tempor diam egestas in. Maecenas dapibus ultrices malesuada. Phasellus
interdum
ante a ligula imperdiet pellentesque. Cras id augue sagittis, malesuada metus nec, eleifend mauris. Mauris
vitae
augue facilisis, ultricies nunc id, lobortis purus. Curabitur nisl purus, placerat ac lacinia sed, vulputate
in
lacus. Mauris at tellus est. Sed sed ipsum in felis vulputate elementum et eget odio. Donec ac dictum diam,
fermentum accumsan nulla. Aenean eget interdum massa, ac congue libero. Donec dapibus vehicula ex, tincidunt
tincidunt purus tempus nec. Praesent metus leo, facilisis sed felis pellentesque, lacinia tincidunt magna.
</p>
<p>
Fusce sit amet hendrerit mi. Sed quis libero at magna facilisis ultricies. Curabitur eget sem et diam
condimentum elementum. Suspendisse elementum sollicitudin augue ac ornare. Nulla eu laoreet dui. Proin nulla
urna, interdum vitae ultricies ac, laoreet in augue. Fusce aliquet aliquet mauris, sit amet malesuada nisl
tempor quis. Duis non ornare mi. Praesent sollicitudin sodales nibh, non malesuada ligula vehicula ut.
</p>
<p>Aliquam non congue nibh. Donec eget magna ante. In bibendum mauris sit amet justo porta consectetur.
Proin et
est vel quam suscipit dictum. Aliquam sed sem sem. Aliquam ac augue sollicitudin, finibus quam in, maximus
erat.
Sed mattis, mi a sollicitudin imperdiet, mauris enim semper metus, viverra mattis risus ex nec tellus.
Phasellus
purus ligula, volutpat vitae sapien eu, molestie rutrum nulla. Quisque dapibus id risus et maximus. Vivamus
sollicitudin dui ut sem maximus, ac pharetra leo accumsan. Quisque efficitur turpis eget vulputate lacinia.
</p>
</div>
</article>

Text highlighter

I've always liked the "Medium" approach to highlighting text and making notes but I never quite knew how it works so I experimented a bit in my spare time. This implementation is not production ready, it has only been tested on latest FF and Chrome where it had noticable bugs.

As said, this was just an exploration into trying to remake Medium's solution and explore the options.

I hope you like it, fork it and hopefully some day use it in production ;)

A Pen by Carl V Lewis on CodePen.

License.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment