- As the container slides to the left, I want each tab to be centered for a brief moment in the container.
- Before the animation begins, I want the very first tab to be centered in the container on page load before the animation begins. I know I need to divide each element's
offsetWidthby 2 so that it is calculated to be centered but I'm not sure where/how to do that.
Link to JS Fiddle: https://jsfiddle.net/nb5a92x3/
window.onload = function() {
const tabsContainer = document.querySelector('.tabsContainer');
const tabs = tabsContainer.querySelectorAll('.tab');
function animate(target) {
let siblings = [];
let elm = target;
let keyframes = [];
let offset;
// Push the widths of each previous element to an array
while (elm = elm.previousElementSibling) {
siblings.push(elm.offsetWidth);
}
// Add the sum of all previous elements
offset = siblings.reduce((prev, current) => prev current, 0);
// Animate the container to the left
setTimeout(function() {
tabsContainer.style.left = `calc(50% - ${offset}px)`;
}, i * 800);
}
// Run the code
for (i = 0; i < tabs.length; i ) {
animate(tabs[i]);
}
}
body {
background: #ccc;
}
section {
text-align: center;
width: 375px;
background: white;
margin: 0 auto;
padding: 16px;
position: relative;
display: flex;
overflow: hidden;
}
.tabsContainer {
display: flex;
position: relative;
margin: 0;
padding: 0;
list-style: none;
gap: 0 16px;
transition: left 0.2s ease;
.tab {
border-radius: 25px;
background: gray;
text-align: center;
height: 34px;
display: inline-flex;
align-items: center;
padding: 0 16px;
}
}
<section>
<ul >
<li >TAB ONE</li>
<li >ANOTHER TAB HERE</li>
<li >A REALLY LONG TAB HERE</li>
<li >LAST TAB</li>
</ul>
</section>
CodePudding user response:
If you set in CSS your container to margin-left: 50%
than all you need is x = EL_curr.offsetLeft EL_curr.offsetWidth / 2
and apply that x value to the slider-container CSS translateX (which is far more performant than animating the CSS left property, since transform can be hardware accelerated).
// Utility functions:
const EL = (sel, par) => (par || document).querySelector(sel);
const ELS = (sel, par) => (par || document).querySelectorAll(sel);
// Tabs animator:
const tabsAnimator = (EL_slider) => {
const ELS_items = ELS(".tab", EL_slider);
const tot = ELS_items.length;
let curr = 0;
let tOut;
const anim = () => {
const EL_curr = ELS_items[curr];
const x = EL_curr.offsetLeft EL_curr.offsetWidth / 2;
EL_slider.style.transform = `translateX(-${x}px)`;
curr = 1; // Increment counter
if (curr > tot - 1) curr = 0; // Reset counter
tOut = setTimeout(anim, 1500);
};
anim(); // Init!
};
ELS(".tabsContainer").forEach(tabsAnimator);
* {
margin: 0;
box-sizing: border-box;
}
.tabsWrapper {
width: 375px;
margin: 20px auto;
padding: 16px 0;
position: relative;
overflow: hidden;
background: #ccc;
border-radius: 3em;
}
.tabsContainer {
position: relative;
padding: 0;
display: inline-flex;
list-style: none;
gap: 0 15px;
transition: transform 0.3s;
margin-left: 50%;
}
.tabsContainer .tab {
border-radius: 25px;
background: gold;
white-space: nowrap;
padding: 5px 15px;
}
<section >
<ul >
<li >5 OFF FRIDAY</li>
<li >10 OFFSUMMER</li>
<li >SAVE MORE</li>
<li >HALF OFF EVERYTHING</li>
</ul>
</section>
<section >
<ul >
<li >JavaScript</li>
<li >HTML</li>
<li >CSS</li>
</ul>
</section>
- don't use horizontal paddings on the wrapper or container slider.
- don't just use
sectionas a selector. It's too common. Use rather a specific class like.tabsWrapperon the wrapper parent.
Additionally, if you'd like to pause the animation on "mouseenter", use clearTimeout(tOut); — and just anim() in the "mouseleave" Event.
