A slider with a stretchy bubble tab indicator. Container flexes to accommodate size of new article. Now with impact bursts. Shout out to GreenSock’s Jack Doyle for an assist on this one. Developed using CSS, HTML and JavaScript. Demo and download options available.
HTML Snippet
<div class="wrap">
<div class="slider"></div>
<svg id="impactBurst" xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 50 50">
<g id="burst" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="4">
<line x1="0" y1="15" x2="40" y2="0" />
<line x1="0" y1="25" x2="40" y2="25" />
<line x1="0" y1="35" x2="40" y2="50" />
</g>
</svg>
<ul class="tabs-block">
<li>Alpha</li>
<li>Bravo</li>
<li>Charlie</li>
<li>Delta</li>
</ul>
</div>
<div class="article-block">
<div class="article">
<h1>Alpha Title</h1>
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Saepe corporis laborum commodi ipsa consectetur blanditiis eum praesentium vero sed, alias totam earum, ut et veniam similique dolor dignissimos autem ad? Eveniet porro quaerat maiores non quibusdam
doloremque ea rerum repellat, sapiente mollitia temporibus neque quas ut odio tempora! Amet, ullam!
</div>
<div class="article">
<h1>Bravo Title</h1>
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Expedita temporibus ab aliquid ipsum cum soluta eos ad molestias neque cumque. Cupiditate laborum nam necessitatibus saepe inventore voluptates soluta? Quisquam, ea!
</div>
<div class="article">
<h1>Charlie Title</h1>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Est, itaque quae! Cupiditate sed consequatur, delectus magni distinctio quam asperiores amet aliquid architecto animi tenetur sit repudiandae deserunt impedit voluptate, corporis eius nobis sequi
dolore adipisci, illum quae soluta! Quam dolor perspiciatis repellat maxime quae molestiae sint a nam exercitationem ipsum maiores praesentium magnam suscipit excepturi illo minima, illum blanditiis nesciunt? Voluptatem quia at provident ad ipsa ratione,
officia sequi error aliquid, expedita consectetur tempore eius et voluptate debitis praesentium beatae libero minus qui. </br></br> Sequi dolore dolor quasi voluptatibus dignissimos iste. Eos pariatur sit vitae perspiciatis voluptas, dolorum quam asperiores
tenetur, earum dignissimos ad veniam? Aliquid excepturi dolorum sed adipisci, iure culpa dolor eos itaque, reprehenderit unde praesentium magnam perspiciatis blanditiis?
</div>
<div class="article">
<h1>Delta Title</h1>
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Veritatis rem quidem amet voluptas quo sit beatae, eum adipisci doloribus repellat?
</div>
</div>
<div class="colors">
<div class="colorBubble"></div>
<div class="colorBubble"></div>
<div class="colorBubble"></div>
<div class="colorBubble"></div>
<div class="colorBubble"></div>
<div class="colorBubble"></div>
<div class="colorBubble"></div>
<div class="colorBubble"></div>
</div>
CSS Code
* {
box-sizing: border-box;
}
body {
padding: 0;
margin: 0;
font-family: "Roboto", sans-serif;
overflow: hidden;
}
.wrap {
position: relative;
width: 700px;
margin: auto;
visibility: hidden;
}
.slider {
position: absolute;
left: 0;
top: 0;
background: #1bb1a5;
box-shadow: 0 2px 5px -1px rgba(0, 0, 0, 0.3);
border-radius: 100px;
height: 100%;
}
.tabs-block {
display: flex;
padding: 0;
flex-direction: row;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 100px;
width: 700px;
height: auto;
margin: 50px auto;
position: relative;
}
li {
text-align: center;
margin: 0;
padding: 10px;
flex: auto;
font-size: 18px;
font-weight: 700;
color: #1bb1a5;
position: relative;
cursor: pointer;
list-style: none;
user-select: none;
}
h1 {
margin: 0;
padding: 0;
color: #1bb1a5;
}
.article-block {
width: 700px;
margin: auto;
overflow: hidden;
box-shadow: 0 10px 12px -12px rgba(0, 0, 0, 0.4) inset;
background: #fff;
border-bottom: 1px solid #1bb1a5;
position: relative;
border-radius: 24px;
visibility: hidden;
}
.article {
position: absolute;
padding: 20px;
left: 0;
top: 0;
line-height: 1.5;
}
.colorBubble {
width: 24px;
height: 24px;
background-color: black;
border-radius: 100%;
margin: 0 2px;
cursor: pointer;
}
.colors {
display: flex;
flex-direction: row;
align-content: center;
align-items: center;
justify-content: center;
margin: 20px auto;
width: 700px;
visibility: hidden;
}
#impactBurst {
position: absolute;
height: 100%;
width: auto;
}
Javascript snippet
claconsole.clear();
TweenLite.defaultEase = Linear.easeNone;
TweenMax.set(".wrap, .article-block, .colors", {autoAlpha:1});
var targets = document.querySelectorAll("li");
var articles = document.querySelectorAll(".article");
var colorArray = ["#1bb1a5", "#94c356", "#e3aa59", "#777", "#a63ba0", "#cf5b21", "#46a4cc", "#000"];
var colorSwatches = document.querySelectorAll(".colorBubble");
var activeTab = 0;
var old = 0;
var heights = [];
var widths = [];
var dur = 0.4;
var burstDur = 0.2;
var animation;
var loopStart = 0;
var loopEnd = 0;
var activeColor = colorArray[0];
for (let i = 0; i < targets.length; i++) {
targets[i].index = i;
heights.push(articles[i].offsetHeight); // get height of each article
widths.push(targets[i].offsetWidth); // get width of each tab
TweenMax.set(articles[i], {top: 0, y:-heights[i]}); // push all articles up out of view
targets[i].addEventListener("click", doCoolStuff);
}
// set initial article and position stretchy bubble slider on first tab
TweenMax.set(articles[0], {y:0});
TweenMax.set(".slider", {x:targets[0].offsetLeft, width:targets[0].offsetWidth});
TweenMax.set(targets[0], {color:"#fff"});
TweenMax.set(".article-block", {height:heights[0]});
TweenMax.set("#burst", {stroke:activeColor});
TweenMax.set("line", {drawSVG:0});
function doCoolStuff() {
// check if clicked target is new and if the timeline is currently active
if(this.index != activeTab) {
//if there's an animation in-progress, jump to the end immediately so there aren't weird overlaps.
if (animation && animation.isActive()) {
animation.progress(1);
}
animation = new TimelineMax();
old = activeTab;
activeTab = this.index;
stretch = 0;
if (activeTab > old) {
loopStart = old;
loopEnd = activeTab;
// moving left to right position the impact burst on the right side of target
animation.set("#impactBurst", {x:targets[activeTab].offsetLeft + widths[activeTab], scaleX:1});
} else {
loopStart = activeTab;
loopEnd = old;
// moving right to left position the impact burst on the left side of target and scaleX:-1
animation.set("#impactBurst", {x:targets[activeTab].offsetLeft, scaleX:-1, transformOrigin:"left center"});
// if moving slider bubble right to left, also animate new x position while stretching
animation.to(".slider", dur, {x:targets[activeTab].offsetLeft, ease:Power2.easeIn}, 0);
}
// get total width of all tabs between new and old (inclusive)
for (let i = loopStart; i < loopEnd + 1; i++) {
stretch += widths[i];
}
// stretch the slider bubble to the start of the new target
animation.to(".slider", dur, {width:stretch, ease:Power2.easeIn}, 0);
// animate the lines of the impact burst
animation.fromTo("line", burstDur/3, {drawSVG:"0% 0%"}, {drawSVG:"0% 40%"}, "springBack");
animation.to("line", burstDur/3, {drawSVG:"70% 100%"});
animation.to("line", burstDur/3, {drawSVG:"100% 100%", ease:Sine.easeOut});
// animate bubble slider to clicked target
animation.to(".slider", dur, {x:targets[activeTab].offsetLeft, width:widths[activeTab], ease:Power2.easeOut}, "springBack");
// change text color on old and new tab targets
animation.to(targets[old], dur, {color:activeColor}, "springBack");
animation.to(targets[activeTab], dur, {color:"#fff"}, "springBack");
// slide current article down out of view and then set it to starting position at top
animation.to(articles[old], dur, {y:heights[old], ease:Back.easeIn }, "springBack");
animation.set(articles[old], {y:-heights[old]});
// resize article block to accommodate new content
animation.to(".article-block", dur, {height:heights[activeTab], ease:Power2.easeOut});
// slide in new article
animation.to(articles[activeTab], 1, {y:0, ease: Elastic.easeOut}, "-=0.25");
}
}
// just stuff related to the color change swatches down here
for (let i = 0; i < colorSwatches.length; i++) {
TweenMax.set(colorSwatches[i], { backgroundColor: colorArray[i] });
colorSwatches[i].index = i;
colorSwatches[i].addEventListener("click", colorChange);
}
function colorChange() {
activeColor = colorArray[this.index];
TweenMax.to(".slider", dur, { backgroundColor: activeColor });
TweenMax.to("h1, li", dur, { color: activeColor });
TweenMax.to(targets[activeTab], dur, { color: "#fff" });
TweenMax.to(".article-block", dur, {borderBottom: "1px solid " + activeColor});
TweenMax.to("#burst", dur, {stroke:activeColor});
}
Preview
Leave a Reply