Stretchy Tab & Springy Content Slider with bursts

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.

Demo Download

Author Craig Roblewsky
Hits
Created SEPTEMBER 16, 2018
License Open
Compatible browsers Chrome, Firefox, Safari

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

Stretchy Tab & Springy Content Slider with bursts

Advertisement

Google Matched Content...