I built a sort of timeline with a grid design and horizontal scrolling.
Dates are in the center row, while a picture is in 1st and some legend in 3rd.
The aim is to display pictures and legends only when their date column stands in the middle of the viewport – using javascript to change the elements’ opacity.
It’s probably not the best approach – but being a newbie and an amateur, I was happy and proud anyway that the code I wrote worked as intended. Well, not exactly…
It works fine on Windows and Android.
But not on iOS: the elements that should become “invisible” after a scroll remain visible on screen, despite having their opacity property set to zero (at least that’s what I see in the Safari inspector window).
The problem seems to be linked to the transitions applied on the opacity property.
If I suppress those transitions, the elements appear/disappear as anticipated even in iOS – just not with the desired fading in / out effect.
After hours of browsing stackoverflow and other forums – I couldn’t find any relevant post on that issue. The closest answers were about hovering and animations and 3Dpreserve, and most were a decade old, inviting to use --webkit prefix. One, however, suggested to apply “backface-visibility: hidden;” on the dynamic elements – but with no more explanation.
As a last resort, I decided to try to apply that solution to my case and – ta da ! It fixed the problem, and now my elements fade in / out nicely in my grid on iOS.
But I can’t help asking myself: wtf? Why would backface-visibility change anything in a code that does no flips and just changes a div opacity? Does anybody know about that issue? Or why would iOS behave like that with transitions?
My problem is fixed, but I’d love to understand why…..
To make things worse, I wasn’t able to replicate the problem in a simplified code: each time I build a simple grid with that same js event listener to display content when a cell reach the viewport’s middle – everything works fine on windows/android/iOS.
So here’s the full thing : github pages
I suspect it has something to do with an error in my event listener:
const timelineWrapper = document.querySelector('.timeline');
const tiles = document.querySelectorAll('button.tl_year');
timelineWrapper.addEventListener('scroll', () => {
const tileWidth = tiles[0].offsetWidth;
tiles.forEach(tile => {
const index = Array.from(tiles).indexOf(tile);
const posTile = (index) * tileWidth;
const endTile = posTile + tileWidth;
const imgSelect = document.querySelector('img[data-year="' + tile.id + '"]');
const legendSelect = document.querySelector('.tl_legende[data-year="' + tile.id + '"]');
const credSelect = document.querySelector('.cred[data-cred="' + tile.id + '"]');
if((timelineWrapper.scrollLeft >= posTile - 10) && (timelineWrapper.scrollLeft < endTile - 10)) {
tile.classList.add('date_active');
imgSelect.classList.add('tl_LegImgVisible');
imgSelect.setAttribute('aria-hidden', false);
legendSelect.classList.add('tl_LegImgVisible');
legendSelect.removeAttribute('inert');
credSelect.classList.add('credShow');
}
else {
tile.classList.remove('date_active');
imgSelect.classList.remove('tl_LegImgVisible');
imgSelect.setAttribute('aria-hidden', true);
legendSelect.classList.remove('tl_LegImgVisible');
legendSelect.setAttribute('inert', true);
credSelect.classList.remove('credShow');
}
})
});
But maybe it’s something else – I don’t know: I’m lost but eager to find out and learn what I did wrong… Thanks in advance for your help!
EDIT Here is a sample of the HTML :
<div class="tl_wrapper leftHidden" role="none">
<div class="timeline" role="region" aria-roledescription="slideshow" aria-labelledby="tl_titre">
<div class="tl_year blank" id="firstTile" aria-hidden="true"></div>
<div class="tl_year blank" aria-hidden="true"></div>
<button class="tl_year date_active" id="1951" data-year="1951" onclick="clicDate(this)" aria-label="Cliquer pour aller vers cette date">1951</button>
<div class="tl_legende" aria-hidden="true"></div>
<div class="tl_legende" aria-hidden="true"></div>
<button class="tl_legende tl_LegImgVisible" data-year="1951" popovertarget="pop_1951">
<h4>Création de la CECA</h4>
<svg class="tl_moreInfo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<!-- some path -->
</svg>
</button>
<!-- x the number of date and legend buttons in the grid -->
<img aria-hidden="false" src="pix/1951.webp" alt="Dernière pages du traité de Paris, avec les signatures et sceaux apposés" data-year="1951" loading="lazy" class="tl_LegImgVisible">
<!-- x the number of images in the grid -->
<div class="credits_photo" aria-hidden="true">
<div class="cred credShow" data-cred="1951">© M. Baronnet / <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">CC BY-SA 4.0</a></div>
<!-- x the number of photo credits to be displayed -->
</div>
</div>
And a sample of the CSS for the elements with changing opacity:
#partChrono img {
height: 90%;
width: auto;
grid-row: 1 / 2;
place-self: center;
opacity: 0;
transition: opacity 1s ease-in-out;
/* backface-visibility: hidden; */
}
.tl_legende {
width: 100%;
grid-row: 3 / 4;
padding-top: 5vmin;
display: flex;
flex-direction: column;
align-items: center;
text-wrap: nowrap;
opacity: 0;
/* backface-visibility: hidden; */
transition: opacity 1s ease-in-out;
}
.tl_LegImgVisible, #partChrono img.tl_LegImgVisible {
opacity: 1;
}
.credits_photo {
display: flex;
align-self: flex-end;
}
.cred {
font-size: var(--fs--2);
font-style: italic;
padding: 1vmin;
display: none
}
.credShow {
display: inline;
}