I am working on a project where I have several hundred spans next to each other, with a letter of the text in each span. When I hover over one of the spans, I want to hide it, as well as the other spans nearby.
It makes an image like this:
@@@@@@@@@@@@@@
@@@@@@@@@@@@@@
@@@@@@@@@@@@@@
@@@@@@@@@@@@@@
My goal is to hide all of the spans in a given distance away from the mouse, like this:
HTML
<span id="overlay-1">@</span>
<!-- ... -->
<span id="overlay-142">@</span>
<span id="overlay-143">@</span>
I'm able to hide 1 of the spans by calling their ids on mouseover and changing the style to display=none, but I want to hide all that are in close proximity to the mouse. Any ideas on what I should do?
I tried to solve this through JS. Here is my code:
function paint() {
let txt = "";
for (let j = 0; j < 100; j++) {
txt += "<div>"
for (let i = 0; i < 100; i++) {
txt += `<span onmouseout="hoverOut()" onmouseover="hover(this)" onmouseuop id="overlay-${i}-${j}">@</span>`
}
txt += "</div>"
}
document.getElementById('painting').innerHTML += txt
}
function hover(x) {
let id = x.id;
let i = x.id.split('-')[1];
let j = x.id.split('-')[2];
for (let a = -2; a <= 2; a++) {
for (let b = -1; b <= 1; b++) {
const elem = document.getElementById(`overlay-${i-a}-${j-b}`);
elem ? elem.style.opacity = 0 : null;
}
}
x.style.opacity = '0';
}
function hoverOut() {
for (let i = 0; i < document.getElementsByTagName('span').length; i++) {
document.getElementsByTagName('span')[i].style.opacity = 1;
}
}
<body onload="paint()">
<div id="painting">
</div>
</body>
Another approach without using ids would be to use Element.getBoundingClientRect() to get the size and position of the hovered element and then use Document.elementFromPoint() inside a loop to access elements near the hovered one:
const main = document.querySelector('main')
for (let i = 0; i < 800; i++) main.innerHTML += '<span>@</span>'
const spans = document.querySelectorAll('span')
const areaWidth = 50
const areaHeight = 50
const hidden = []
function getElements(currentSpan, color) {
const { top, right, bottom, left, width, height } = currentSpan.getBoundingClientRect()
for (let col = left - areaWidth / 2; col < right + areaWidth / 2; col += width || 14) {
for (let row = top - areaHeight / 2; row < bottom + areaHeight / 2; row += height || 14) {
const el = document.elementFromPoint(col, row)
if (el?.tagName === 'SPAN') {
el.style.color = color
hidden.push(el)
}
}
}
}
spans.forEach(span => {
span.addEventListener('mouseover', () => getElements(span, 'transparent'))
span.addEventListener('mouseout', () => {
hidden.forEach(el => (el.style.color = ''))
hidden.length = 0
})
})
main {
display: flex;
flex-wrap: wrap;
width: 640px;
cursor: default;
}
<main></main>
You could use a CSS solution - overwrite the adjacent characters with a pseudo element on the clicked character.
This snippet uses a monospace font and it's set line height and letter spacing as CSS variables so you can alter them as required.
function clicked(ev) {
ev.target.classList.add('obscure');
}
const container = document.querySelector('.container');
for (let i = 0; i < 200; i++) {
const span = document.createElement('span');
span.innerHTML = '@';
if (i % 10 == 0) {
container.innerHTML += '<br>';
}
container.appendChild(span);
}
container.addEventListener('click', clicked);
.container {
width: 50vw;
height: auto;
font-family: Courier, monospace;
--line-height: 20px;
--letter-spacing: 5px;
line-height: var(--line-height);
letter-spacing: var(--letter-spacing);
}
.container span {
position: relative;
margin: 0;
padding: 0;
}
.obscure::before {
content: '';
width: calc(5ch + (6 * var(--letter-spacing)));
height: calc(3 * var(--line-height));
background-color: white;
position: absolute;
top: 0;
transform: translate(calc(-50% + 0.5ch), calc(-50% + (1ch)));
left: 0;
z-index: 1;
display: inline-block;
}
<body>
<div class="container"></div>
</body>