<main class="main-content">
    <h1 class="title-content">Expandable Animation Keyframe</h1>
    <article class="middle-content">
      <div class="section-item-wrapper">
          <div class="section is--collapsed">
            <div class="section-item">
              <button id="section-title" aria-expanded="false" aria-controls="section-content" class="section-item-title">
                Rutger Hauer
                <i class="section-item-title-icon fas fa-chevron-down"></i>
              </button>
              <div id="section-content" role="region" tabindex="-1" aria-labelledby="section-title" class="section-item-content" aria-hidden="true">
<q>"I've seen things you people wouldn't believe. Attack ships on fire off the shoulder of Orion. I watched C-beams glitter in the dark near the Tannhäuser Gate. All those moments will be lost in time, like tears in rain."</q>
              </div>
            </div>
        </div>
      </div>
    </article>
    <article class="bottom-content">
      <a class="social-icon" title="Go to Twitter" target="_blank" href="https://twitter.com/Bernardoagc1990">
        <i class="fab fa-twitter"></i>
      </a>
      <a class="social-icon" title="Go to Linkedin" target="_blank" href="https://www.linkedin.com/in/bernardo-cardoso-0a740b95/">
        <i class="fab fa-linkedin-in"></i>
      </a>
      <a class="social-icon" title="Go to Medium" target="_blank" href="https://medium.com/@bernardocardoso">
        <i class="fab fa-medium-m"></i>
      </a>
    </article>  
</main> 


/* Design Systems styles variables */
:root {
  /* Colors */
  --color-primary: #1068eb;
  --color-background: #f4f4f4;
  --color-neutral: #6e6c6c;

  /* Spacing */
  --space-xs: 4px; 
  --space-s: 8px;
  --space-base: 16px;

  /* Font */
  --font-family: "Nunito Sans", Arial, Helvetica, sans-serif;
  --font-size-base: 16px;
  --font-size-m: 24px;
  --heading-1: 36px; 

  /* Styles */ 
  --border-radius-soft: 4px;
  --shadow-positive: 3px 3px 6px #bebebe, -3px -3px 6px #fff;
  --shadow-negative: inset 3px 3px 5px #cbcbcb, inset -3px -3px 5px #fff;
}

/* Resets */
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

/* Html structure and grid*/
html, body {
  background-color: var(--color-background);
  font-family: var(--font-family);
  font-size: var(--font-size-base);
  height: 100%;
  width: 100%;
}

.main-content {
  display: grid;
  grid-template-rows: 200px 1fr 1fr;
  height: 100%;
  padding-top: var(--space-base);
  width: 100%;
}

.main-content .middle-content,
.main-content .title-content,
.main-content .bottom-content {
  align-self: center;
  justify-self: center;
}

.title-content {
  font-size: var(--heading-1);
  text-align: center;
}

.middle-content {
  min-height: 200px;
  width: 300px;
}

.bottom-content {

}

.bottom-content .social-icon {
  align-items: center;
  box-shadow: var(--shadow-positive);
  color: var(--color-neutral);
  display: inline-flex;
  height: 52px;
  justify-content: center;
  margin-right: var(--space-base);
  padding: var(--space-base);
  text-decoration: none;
  width: 52px;
}

.bottom-content .social-icon:hover {
  box-shadow: var(--shadow-negative);
  color: var(--color-primary);
}

/* Section Pattern Styles*/
.section-item-wrapper {
  min-height: var(--title-height);
  position: relative;
}

.section {
  animation-duration: 600ms;
  animation-timing-function: step-end;
  background: linear-gradient(145deg, #fff, #dcdcdc);
  box-shadow: var(--shadow-positive);
  contain: content;
  left: 0;
  position: absolute;
  top: 0;
  transform-origin: top left;
  will-change: transform;
}

.section.is--expanded {
  background: var(--color-background);
  box-shadow: var(--shadow-negative);
  position: static;
}

.section-item {
  animation-duration: 600ms;
  animation-timing-function: step-end;
  contain: content;
  transform-origin: top left;
  will-change: transform;  
}

.section-item-title {
  align-items: center;
  background-color: transparent;
  border: none;
  color: var(--color-neutral);
  cursor: pointer;
  display: inline-flex;
  justify-content: space-between;
  font-family: inherit;
  font-size: var(--font-size-m);
  padding: var(--space-base);
  width: 100%;
}

.section-item-title-icon {
  color: var(--color-primary);
  font-size: var(--font-size-base);
  transition: transform 200ms cubic-bezier(0.4, 0, 1, 1);
  transform-origin: center;
}

.is--expanded .section-item-title-icon {
  transform: rotate(180deg); 
}

.section-item-content {
  color: var(--color-neutral);
  opacity: 1;
  padding: var(--space-base);
  transition: opacity 500ms ease;
}

.is--collapsed .section-item-content {
  opacity: 0;
  visibility: hidden;
}

/* Animations */
.section.is--expanded {
  animation-name: var(--sectionExpandAnimation);
}

.is--expanded .section-item {
  animation-name: var(--sectionExpandContentsAnimation);
}

.section.is--collapsed {
  animation-name: var(--sectionCollapseAnimation);
}

.is--collapsed .section-item {
  animation-name: var(--sectionCollapseContentsAnimation);
}

// Constructor based on Google's ChromeLabs https://github.com/GoogleChromeLabs/ui-element-samples
class section {
    constructor (elem, index) {
      this._section = elem;
      this._sectionWrapper = this._section.parentNode;
      this._sectionItem = this._section.querySelector('.section-item');
      this._sectionItemTitle = this._section.querySelector('.section-item-title');
      this._sectionContent = this._section.querySelector('.section-item-content');
  
      this._expanded = true;
      this._animate = false;
      this._nFrames = 60;
      this._collapsed;
      this._index = index;
  
      this.expand = this.expand.bind(this);
      this.collapse = this.collapse.bind(this);
      this.toggle = this.toggle.bind(this);
  
      this._calculateScales();
      this._createEaseAnimations(index);
      this._addEventListeners();
  
      this.collapse();
      this._animate = true;
      
      this._sectionExpandAnimationName = '';
      this._sectionExpandContentsAnimationName = '';
      this._sectionCollapseAnimationName = '';
      this._sectionCollapseContentsAnimationName = '';
  
    }
  
    collapse () {
      if (!this._expanded) {
        return;
      }
      this._expanded = false;
  
      var y = this._collapsed.y;
      var invY = 1 / y;
      
      this._section.style.transform = `scaleY(${y})`;
      this._sectionItem.style.transform = `scaleY(${invY})`;
      this._handleAccessbility(false);
  
      if (!this._animate) {
        return;
      }
  
      this._applyAnimation({expand: false});
    }
  
    expand () {
      if (this._expanded) {
        return;
      }
      this._expanded = true;
  
      this._section.style.transform = `scaleY(1)`;
      this._sectionItem.style.transform = `scaleY(1)`;
      this._handleAccessbility(true);
  
      if (!this._animate) {
        return;
      }
      
      this._applyAnimation({expand: true});
      
    }
  
    toggle () {
      
      if (this._expanded) {
        requestAnimationFrame(this.collapse);
        return;
      }
      
      requestAnimationFrame(this.expand);

    }
  
    _handleAccessbility (isExpand) {
      var tabindexValue = isExpand ? 0 : -1; 
      this._sectionItemTitle.setAttribute('aria-expanded', isExpand);
      this._sectionContent.setAttribute('aria-hidden', !isExpand);
      this._sectionContent.setAttribute('tabindex', tabindexValue);
    }
    
    _addEventListeners () {
      this._sectionItemTitle.addEventListener('click', this.toggle);
    }
  
    _applyAnimation ({expand}=opts) {
      this._section.classList.remove('is--expanded');
      this._section.classList.remove('is--collapsed');
  
      if (expand) {
        this._section.classList.add('is--expanded');
        return;
      }
  
      this._section.classList.add('is--collapsed');
      
    }
  
    _calculateScales () {
      var collapsed = this._sectionItemTitle.getBoundingClientRect();
      var expanded = this._section.getBoundingClientRect();
      
      // create css variable with collapsed height, to apply on the wrapper
      this._sectionWrapper.style.setProperty('--title-height', parseInt(collapsed.height) + 'px');

      this._collapsed = {
        y: collapsed.height / expanded.height
      }
    }
  
    _createEaseAnimations (index) {
      var sectionEase = document.querySelector('.section-animations');
      if (!sectionEase) {
        sectionEase = document.createElement('style');
        sectionEase.classList.add('section-animations');
      }

      var sectionExpandAnimation = [];
      var sectionExpandContentsAnimation = [];
      var sectionCollapseAnimation = [];
      var sectionCollapseContentsAnimation = [];
  
      var percentIncrement = 100 / this._nFrames;
  
      for (var i = 0; i <= this._nFrames; i++) {
        var step = this._ease(i / this._nFrames).toFixed(5);
        var percentage = (i * percentIncrement).toFixed(5);
        var startY = this._collapsed.y;
        var endY = 1;
  
        // Expand animation.
        this._append({
          percentage,
          step,
          startY,
          endY,
          outerAnimation: sectionExpandAnimation,
          innerAnimation: sectionExpandContentsAnimation
        });
  
        // Collapse animation.
        this._append({
          percentage,
          step,
          startY: 1,
          endY: this._collapsed.y,
          outerAnimation: sectionCollapseAnimation,
          innerAnimation: sectionCollapseContentsAnimation
        });
      }
      
      // Create unique Animation names, useful for multiple section patterns
      this._createAnimationsNames();
      
      sectionEase.textContent += `
      @keyframes ${this._sectionExpandAnimationName} {
        ${sectionExpandAnimation.join('')}
      }
  
      @keyframes ${this._sectionExpandContentsAnimationName} {
        ${sectionExpandContentsAnimation.join('')}
      }
  
      @keyframes ${this._sectionCollapseAnimationName} {
        ${sectionCollapseAnimation.join('')}
      }
  
      @keyframes ${this._sectionCollapseContentsAnimationName} {
        ${sectionCollapseContentsAnimation.join('')}
      }`;
  
      document.head.appendChild(sectionEase);
      return sectionEase;
    }
  
    _append ({
          percentage,
          step,
          startY,
          endY,
          outerAnimation,
          innerAnimation}=opts) {
  
    
      var yScale = (startY + (endY - startY) * step).toFixed(5);
  
      var invScaleY = (1 / yScale).toFixed(5);
  
      outerAnimation.push(`
        ${percentage}% {
          transform: scaleY(${yScale});
        }`);
  
      innerAnimation.push(`
        ${percentage}% {
          transform: scaleY(${invScaleY});
        }`);
    }
  
    _createAnimationsNames () {
      
      this._sectionExpandAnimationName = "sectionExpandAnimation" + this._index;
      this._sectionExpandContentsAnimationName = "sectionExpandContentsAnimation" + this._index;
      this._sectionCollapseAnimationName = "sectionCollapseAnimation" + this._index;
      this._sectionCollapseContentsAnimationName = "sectionCollapseContentsAnimation" + this._index;
      
      // Create CSS Var of each animation
      this._section.style.setProperty('--sectionExpandAnimation', this._sectionExpandAnimationName);
      this._section.style.setProperty('--sectionExpandContentsAnimation', this._sectionExpandContentsAnimationName);
      this._section.style.setProperty('--sectionCollapseAnimation', this._sectionCollapseAnimationName);
      this._section.style.setProperty('--sectionCollapseContentsAnimation', this._sectionCollapseContentsAnimationName);
    }
  
    _clamp (value, min, max) {
      return Math.max(min, Math.min(max, value));
    }
  
    _ease (v, pow=4) {
      v = this._clamp(v, 0, 1);
  
      return 1 - Math.pow(1 - v, pow);
    }
}

var sections = document.querySelectorAll('.section');
var currentsection;

for(var i = 0; i < sections.length; i++){
   currentsection = sections[i];
   new section(currentsection, i); 
}

 
  
  

External CSS

  1. https://fonts.googleapis.com/css?family=Nunito+Sans

External JavaScript

This Pen doesn't use any external JavaScript resources.