I have been playing with a pure html-css gantt-styled chart. Still a lot to go.
But I can't find the solution to trigger "hover" on the time-axis vertical divs, which are position absolute, and are below all the contents drawn by each row.
Hovering the rows works as expected and also on their child-bars, but obstructs overlapped elements to be triggered.
I quickly made this shortened example on fiddle https://jsfiddle.net/diegoj2020/52snopkb/17/
My objective is to highlight the date where the mouse is pointing at.
I am trying to avoid javascript for layouting or styling. I would like to find the pure-css method if any.
In the fiddle example, the expected behavior is to also highlight the vertical bars when the mouse sits on any row.
Any help would be very appreciated.
/*
The attempt her eis to highlight the row, the column, and the insight bar/object, all of 3 at the same time when the mouse is over.
*/
* {
margin: 0;
padding: 0;
}
xy-plot {
position: absolute;
width: 95%;
border: 1px solid red;
padding: 5px;
margin: 10px;
}
cols {
position: absolute;
display: block;
left: 100px;
right: 10px;
height: 100%;
}
rows {
position: relative;
display: block;
margin-top: 30px;
}
day {
box-sizing: border-box;
display: inline-block;
background-color: #2222;
;
width: calc(100% / 365);
height: 100%;
}
day:nth-child(even) {
background-color: #5552;
}
task {
display: block;
position: relative;
}
taskbar {
display: inline-block;
position: absolute;
left: 250px;
width: 100px;
background-color: red;
height: 100%;
border-radius: 8px;
}
task:hover {
background-color: darkgray;
}
day:hover {
background-color: darkgray;
}
taskbar:hover {
background-color: black;
}
<xy-plot>
<cols>
<script>
// populate columns time-axis
document.write('<day></day>'.repeat(365));
</script>
</cols>
<rows>
<script>
// populate rows and inside elements
document.write('<task>A Task<taskbar></taskbar></task>'.repeat(10));
</script>
</rows>
</xy-plot>
The heart of your question is the ability for a mouse hover to apply to all elements in the z-index at the pointer location.
CSS does not support this. By default, :hover
is only triggered on the element in front, or with the highest z-index, at the current pointer location.
body {
margin: 1em;
}
.d {
display: inline-block;
margin-right: 3em;
}
.d > * {
width: 6em;
aspect-ratio: 1;
opacity: 0.7;
}
.d > :first-child {
background: red;
}
.d > :last-child {
background: green;
margin-top: -3em;
margin-left: 3em;
}
.d > :hover {
outline: 5px solid black;
}
.d2 > :last-child {
pointer-events: none;
}
<div class="d">
<div></div>
<div></div>
</div>
The only control CSS supports is the ability to hide an element from all mouse interaction by styling it with pointer-events: none
. This effectively makes the element invisible to the mouse pointer, so :hover
will trigger on the next-highest element in the z-order. (Of course, this won’t be of any help in your scenario.)
body {
margin: 1em;
}
.d {
display: inline-block;
margin-right: 3em;
}
.d > * {
width: 6em;
aspect-ratio: 1;
opacity: 0.7;
}
.d > :first-child {
background: red;
}
.d > :last-child {
background: green;
margin-top: -3em;
margin-left: 3em;
pointer-events: none;
}
.d > :hover {
outline: 5px solid black;
}
<div class="d">
<div></div>
<div></div>
</div>
To solve it you will need to use script, because the DOM provides the method document.elementsFromPoint(x, y)
which returns all elements at the specified coordinates. Set up a mousemove
event listener on the parent element which calls this method.
const d = document.querySelector('.d')
const hasHoverClass = () => {
return Array.from(d.querySelectorAll('.hover'))
}
const removeHover = arr => {
arr.forEach(a => {
a.classList.remove('hover')
})
}
d.addEventListener('mousemove', evt => {
const hovCls = hasHoverClass()
const moused = document.elementsFromPoint(evt.clientX, evt.clientY)
moused.forEach(a => { // for each element under the mouse
const i = hovCls.indexOf(a) // is it already styled hovered?
if (i == -1) // no ...
a.classList.add('hover') // ... so style it hovered
else // yes ...
hovCls.splice(i,1) // ... so leave it alone
})
removeHover(hovCls) // clear the hover from the rest
})
d.addEventListener('mouseout', evt => {
removeHover(hasHoverClass())
})
body {
margin: 1em;
}
.d {
display: inline-block;
margin-right: 3em;
}
.d > * {
width: 6em;
aspect-ratio: 1;
opacity: 0.7;
}
.d > :first-child {
background: red;
}
.d > :last-child {
background: green;
margin-top: -3em;
margin-left: 3em;
}
.d > .hover {
outline: 5px solid black;
}
<div id="x" class="d">
<div id="y"></div>
<div id="z"></div>
</div>
Now take that logic and apply it to your example, as follows:
const d = document.querySelector('xy-plot')
const tags = ['DAY','TASK','TASKBAR']
const hasHoverClass = () => {
return Array.from(d.querySelectorAll('.hover'))
}
const removeHover = arr => {
arr.forEach(a => {
a.classList.remove('hover')
})
}
d.addEventListener('mousemove', evt => {
const hovCls = hasHoverClass()
const moused = document.elementsFromPoint(evt.clientX, evt.clientY).filter(a => {return tags.includes(a.nodeName)})
moused.forEach(a => { // for each element under the mouse
const i = hovCls.indexOf(a) // is it already styled hovered?
if (i == -1) // no ...
a.classList.add('hover') // ... so style it hovered
else // yes ...
hovCls.splice(i,1) // ... so leave it alone
})
removeHover(hovCls) // clear the hover from the rest
})
d.addEventListener('mouseout', evt => {
removeHover(hasHoverClass())
})
* {
margin: 0;
padding: 0;
}
xy-plot {
position: absolute;
width: 95%;
border: 1px solid red;
padding: 5px;
margin: 10px;
}
cols {
position: absolute;
display: block;
left: 100px;
right: 10px;
height: 100%;
}
rows {
position: relative;
display: block;
margin-top: 30px;
}
day {
box-sizing: border-box;
display: inline-block;
background-color: #2222;
width: calc(100% / 100);
height: 100%;
}
day:nth-child(even) {
background-color: #5552;
}
task {
display: block;
position: relative;
}
taskbar {
display: inline-block;
position: absolute;
left: 250px;
width: 100px;
background-color: #ff00008f;
height: 100%;
border-radius: 8px;
}
day.hover, task.hover {
background-color: yellow;
}
taskbar.hover {
background-color: pink;
}
<xy-plot>
<cols>
<script>
// populate columns time-axis
document.write('<day></day>'.repeat(100));
</script>
</cols>
<rows>
<script>
// populate rows and inside elements
document.write('<task>A Task<taskbar></taskbar></task>'.repeat(10));
</script>
</rows>
</xy-plot>