Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using it's URL and the proper URL extention.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <header>
  <h1>Peek-a-boo v7 demo</h1>

  <p>Status:  <strong>Failed testing 12-07-2016</a>. Currently rewriting. In the meantime use <a href="http://websemantics.uk/articles/peek-a-boo/" title="[new window]" target="_blank">version 6</a> or see <a href="https://codepen.io/2kool2/pen/LkaXay?editors=0100" title="[new window]" target=_blank>WCAG ARIA accessible pop-up iframe modal dialog (v4)</a>.</p>
  <p>Material design inspired animated show hide technique which is fully <del>WCAG compliant</del> and stores open states in local storage.</p>
  <p>Modular designed vanilla JavaScript.<br>Google Closure compiles to 1.25KB gzipped (3.19KB uncompressed).</p>
</header>

<main>

  <section class=pab_container>
    <h2 data-pab=feds>Federation ipsum</h2>
    <div id=feds>
      <p>Electromagnetic and subspace wave fronts approaching synchronization. I have reset the sensors to scan for frequencies outside the usual range. By emitting harmonic vibrations to shatter the lattices. We will monitor and adjust the frequency of the resonators.</p>
      <p>Stellar flares are increasing in magnitude and frequency. Run a manual sweep of anomalous airborne or electromagnetic readings. </p>
      <p><a target=_blank title="[new window]" href="http://star-trek-ipsum.meetpollux.com/">Trekkie Ipsum</a></p>
    </div>
  </section>

  <section class=pab_container>
    <h2 data-pab=klingon>Klingon ipsum</h2>
    <div id=klingon lang="klingon">
      <p>TlhIngan Hol vIjatlhtaHvIS, chay' "cock" vIjatlh?</p>
      <ul>
        <li>Qapla'! Bachha' bis'ub boqha' chegh denibya' ghay'cha' je' mara mil pa' beb pivlob qanrad qaywi' qin rech senwi' rilwi' je serrum tagha' vin wab 'iq.</li>
        <li>Bertlham butlh da'nal duran lung dir hom jolpat ma'veq mevyap mu'qad ves qo'qad sa'hut tagha' taq yer 'edsehcha.</li>
      </ul>
      <p><a target=_blank title="[new window]" href="http://panipsum.com/">Klingon Ipsum</a>: Hab SoSlI’ Quch!</p>
    </div>
  </section>
  
</main>
              
            
!

CSS

              
                /* Generic styling */

* {box-sizing: border-box;}
body {
  margin: 2rem auto;
  max-width: 34rem;
  color: #3a2c49;
  background-color: #eee;
  font-family: sans-serif;
  line-height: 1.5;
  text-align: center;
  min-height: 200vh;
  
  /* SVG not checked in IE */
  background-image: url("data:image/svg+xml;utf8,<svg style='opacity:.75' width='1200' height='256' viewBox='0 0 1200 256' xmlns='http://www.w3.org/2000/svg'><defs><filter id='noise'><feTurbulence type='fractalNoise' baseFrequency='.4'></feTurbulence></filter></defs><rect height='256' width='1200' filter='url(%23noise)' opacity='0.3'/></svg>");
}
h1,h2 {font-weight: 100;}
p {margin: 1rem 0;}


/* Links revisited - https://codepen.io/2kool2/pen/BKVOmW */

a {
  color: #236ECE;
  padding: 0 1px;
  text-decoration: none;
  border: 1px solid transparent;
  border-bottom-color: rgba(35,110,206,.25);
  transition:
    box-shadow .1s ease-in,
    background-color .1s ease-in,
    border-color .1s ease-in;
}
a:visited {
  color: #236ECE;
  border-bottom-color: rgba(35,110,206,.5);
}
a:hover,
a:focus,
a:active {
  background-color: #fff;
}
a:hover,
a:active {
  border-bottom-color: #236ECE;
  box-shadow: 0 0 .125rem .25rem #fff;
}
a:focus {
  outline: 0 solid;
  border-color: #fff;
  border-radius: .25rem;
  box-shadow: 0 0 .125rem .25rem rgba(64, 128, 255, 0.7);
}


/* Peek-a-boo */

/* Peek-a-boo Container */

.pab_container {
  text-align: left;
  margin: 1.618rem 0;
  border: 1px solid rgba(96,96,128,.1);
  position: relative;
  background-color: rgba(255,255,255,.3);
  transition:
    background-color .3s ease-out,
    box-shadow .3s ease-out;
}
.pab_btn_hovered,
.pab_btn_focused {
  background-color: rgba(255, 255, 255, 0.65);
  box-shadow: 0 4px 4px rgba(0,0,0,0.3);
}

/* Without JS available */

.pab_container > :first-child {
  text-align: left;
  font-size: 1.118rem;
  padding: 0.618rem 1rem;
  margin: 0;
  background-color: transparent;
  border: 1px solid rgba(255, 255, 255, 0.7);
}
.pab_container > :nth-child(2),
.pab_container > :nth-child(3) {
  /* :nth-child(3) added so padding is applied to the clone when calculating max-height */
  padding: 0 1rem;
  list-style: none; /* in case it's a ul or ol */
}
.pab_container li {
  margin: 1rem 0;
}

/* has JS available so immediately reserve SVG space, before JS kicks in */
.hasJS [data-pab] {
  /* Using absolute positioning for SVG, so padding required */
  padding-left: 2.618rem;
}

/* 3d acceleration - not required but what the hey */
.spot,
[data-pab] + [aria-hidden] {
   transform: translate3d(0, 0, 0);
   backface-visibility: hidden;
   perspective: 1000;
}


/* After the JS has loaded and set up the attributes */

/* toggle button */
[data-pab][role=button] {
  cursor: pointer;
  position: relative;
  overflow: hidden;
  touch-action: manipulation; /* ??? */
  transition: box-shadow .2s ease-in, background-color .2s ease-in, color .3s ease-in;

  /* using absolute positioning for svg */
  padding: 0.618rem .236rem .618rem 2.618rem;
}
[data-pab][role=button]:hover,
[data-pab][role=button]:focus,
[data-pab][role=button]:active {
  color: #236ECE;
  background-color: #fff;
}
[data-pab][role=button]:focus {
  outline: 0 solid;
}
[data-pab][aria-expanded=true] {
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
}
[data-pab] * {
  pointer-events: none;
}


/* SVG plus */

.svg-plus {
  display: block;
  position: absolute;
  top: calc(50% - 1rem);
  left: 0.618rem;
  width: 1.8rem;
  height: 1.8rem;
  margin: 0;
  transition: transform .7s ease-out;
  pointer-events: none;
}
.svg-plus > path {
  stroke-width: 5;
  stroke-linecap: square;
  stroke: #9F9DB2;
  -webkit-transition: stroke .5s ease-out,opacity .7s ease-out;
  transition: stroke .5s ease-out,opacity .7s ease-out;
}
[aria-expanded=true] > .svg-plus {
  transform: rotateZ(360deg);
}
[aria-expanded=true] > .svg-plus > :last-child {
  opacity: 0;
}
[data-pab]:hover > .svg-plus > path,
[data-pab]:focus > .svg-plus > path {stroke:#418cec}


/* Open / close animation - problem is the inaccurate max-height - resolved via JS */
[data-pab] + [aria-hidden] {
  overflow: hidden;
  opacity: 1;
  max-height: 50rem;
  visibility: visible;
  transition:
    visibility 0s ease 0s,
    max-height .65s ease-out 0s,
    opacity .65s ease-in 0s;
}
[data-pab] + [aria-hidden=true] {
  max-height: 0;
  opacity: 0;
  visibility: hidden;
  transition-delay: 1s, 0s, 0s;
}
/* Overide max-height set as an inline style by the JS */
[data-pab] + [style][aria-hidden=true] {
  max-height: 0 !important;
}

/*Used for testing animation smoothness only */
.xxx[data-pab] + [aria-hidden] > :last-child {
  margin-bottom: 0;
  padding-bottom: 1rem;
}
.xxx[data-pab] + [aria-hidden] > :first-child {
  margin-top: 0;
  padding-top: 1rem;
}




/* Spot animation */

.spot {
  display: block;
  position: absolute;
  background: rgba(35,110,206,0.35);
  border-radius: 50%;
  transform: scale(0);
  opacity: 1;
  -webkit-filter: blur(1rem);
  filter: blur(1rem);
}
[aria-expanded=true] .spot {animation: spot-open .6s ease-in;}
[aria-expanded=false] .spot {animation: spot-close .6s ease-out;}

@keyframes spot-open {
  to {
    opacity: 0;
    transform: scale(2.4);
  }
}
@keyframes spot-close {
 0% {
    opacity: 0;
    transform: scale(2.4);
  }
  100% {
    opacity: 1;
    transform: scale(0);
  }
}
              
            
!

JS

              
                
// https://john-dugan.com/javascript-debounce/
// https://codepen.io/johndugan/pen/BNwBWL?editors=001
var debounce = function(func, wait, immediate) {
  var timeout;
  return function() {
    var context = this;
    var args = arguments;
    var later = function() {
      timeout = null;
      if ( !immediate ) {
        func.apply(context, args);
      }
    };
    var callNow = immediate && !timeout;
    clearTimeout(timeout);
    timeout = setTimeout(later, wait || 200);
    if ( callNow ) {
      func.apply(context, args);
    }
  };
};


// replace this with a real test!
var useLocalStorage = true;

var svg_plus = '<svg class=svg-plus width=38 height=38 viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg"><title>Show</title><path d="M10.5 19l17 0"/><path d="M19 10.5l0 17"/></svg>';

// peek-a-boo.7.js

// Show - hide a block
// Requires:
//    setAttribute / getAttribute (IE9+)
//    classList (IE10+)
//    addEventListener (IE9+)
//    requestAnimationFrame (IE10+)
//    querySelectorAll
//    preventDefault

// useLocalStorage - global boolean

// v5.0 Simplifies button to a more Material Design look and feel
// v5.1 Adds requestAnimationFrame to classList changes which have visual consequence
//      Removes .hidden from keyboard chain on initialisation via display:none using displayClass
// v5.2 Keep heading as button container
//      Spot limited to button area only
//      CSS: SVG hover colour and animations improved
// v5.3 Simplify html, unify material design clickable objects
// v5.4 Adds class to heading once button added - to balance layout before and after
// v5.5 Adds audio to button animation
// v5.5a Remove audio, add hover state to container
// v6   Externalised svg, d=document, debounce.
//      local storage only used on regions (.hidden) that have an id.
//      window resize now changes the open regions max-height value.
// v7   Class names are depricated in favour of ARIA attributes.
//      Scripting far more modular - a full rewrite.
//      Bits stolen from - https://github.com/edenspiekermann/a11y-toggle/blob/master/a11y-toggle.js

// Accessibility references:
// http://test.cita.illinois.edu/aria/hideshow/hideshow1.php#lsc2
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_button_role

// TODO:
// multiple objects add remove class (navigation, main)

// When Google closure compiling:
//  leave out the "var ", and add "peekaboo.add(obj)" at the bottom


var Pab = (function (svg_plus, useLocalStorage) {

  // Uses:
  //    querySelectorAll, preventDefault, classList,
  //    setAttribute, getAttribute, addEventListener, requestAnimationFrame
  // Required externals:
  //    svg_plus - definition of SVG icon as string
  //    useLocalStorage - boolean
  //    debounce() - debounce function used for window resize

  "use strict";

  var dataAttr = "data-pab";
  var spotClass = "spot";
  var attrName = dataAttr.replace("data-", "") + "_";
  var internalId = 0;


  function $ (selector) {
    return Array.prototype.slice.call(document.querySelectorAll(selector));
  }


  function _isExpanded (obj) { // or not aria-hidden
    return obj && (obj.getAttribute("aria-expanded") === "true" || obj.getAttribute("aria-hidden") === "false");
  }


  var _getHiddenObjectHeight = function (obj) {
    var clone = obj.cloneNode(true);
    var height = 0;
    clone.setAttribute('style',
                       "display:block;"
                       + "width:" + obj.clientWidth + "px;"
                       + "position:absolute;"
                       + "top:0;"
                       + "left:-999rem;"
                       + "max-height:none;"
                       + "height:auto;"
                       + "visibility:hidden;"
                      );
    obj.parentElement.appendChild(clone);
    height = clone.clientHeight; // faster than getBoundingClientRect
    //console.log("height:" + clone.clientHeight + "px, ","width:" + obj.clientWidth + "px");
    obj.parentElement.removeChild(clone);
    return height;
  };


  var _setLocalStorage = function (key, value) {
    if (useLocalStorage) {
      if (value) {
        localStorage["target_" + key] = value;
        //console.log("add ", "target_" + key, value);
      } else {
        localStorage.removeItem("target_" + key);
        //console.log("remove ", "target_" + key);
      }
    }
  };


  var _openFromLocalStorage = function () {
    if (useLocalStorage) {

      // A 1 second delay to allow onload animations, or page build, to complete. Looks better too.
      setTimeout(function () {
        var key;
        var value;
        var target;
        var toggle;
        var i = localStorage.length;
        while (i--) {
          key = localStorage.key(i);
          if (key.match("target_")) {
            value = localStorage.getItem(key);
            toggle = document.getElementById(key.replace("target_", ""));
            target = document.getElementById(value);
            if (target && toggle) {
              //console.log("open ",target, toggle);
              _openCloseToggleTarget(toggle, target, false);
            }
          }
        }
      }, 1000);
    }
  };


  var _toggleParentClass = function (event) {
    var cls = attrName + "btn_";
    switch (event.type) {
      case "focus" :
        event.target.parentNode.classList.add(cls + "focused");
        break;
      case "blur" :
        event.target.parentNode.classList.remove(cls + "focused");
        break;
      case "mouseover" :
        event.target.parentNode.classList.add(cls + "hovered");
        break;
      case "mouseout" :
        event.target.parentNode.classList.remove(cls + "hovered");
        break;
    }
  };


  var _activateSpot = function (event, toggle) {
    var w = toggle.clientWidth;
    var x = event.offsetX - (w / 2);
    var y = event.offsetY - (w / 2);
    var spot = document.getElementById(toggle.spotId);

    if (event.type === "keydown") {
      x = 0;
      y = -(w - toggle.clientHeight) / 2;
    }

    window.requestAnimationFrame(function () {
      spot.setAttribute("style",
        "top:" + y + "px;"
        + "left:" + x + "px;"
        + "height:" + w + "px;"
        + "width:" + w + "px"
      );
    });
  };


  var _setToggleMaxHeight = function (target) {
    if (_isExpanded(target)) {
      // max-height overidden by CSS
      //target.style.maxHeight = 0;
    } else {
      target.style.maxHeight = _getHiddenObjectHeight(target) + "px";
    }
  };


  var _setToggleSvgTitle = function(toggle) {
    var title = toggle.getElementsByTagName("title");
    if (title && title[0]) {
      title[0].innerHTML = _isExpanded(toggle) ? "Hide" : "Show";
    }
  };


  var _openCloseToggleTarget = function (toggle, target, isExpanded) {
    toggle.setAttribute("aria-expanded", !isExpanded);
    _setToggleMaxHeight(target);
    target.setAttribute("aria-hidden", isExpanded);
    _setToggleSvgTitle(toggle);
  };


  var _toggleClicked = function (event) {

    var toggle = event.target;
    var target;
    var isExpanded;

    if (toggle) {
      target = document.getElementById(toggle.getAttribute("aria-controls"));
      if (target) {
        event.preventDefault();
        isExpanded = _isExpanded(toggle);
        _openCloseToggleTarget(toggle, target, isExpanded);
        _activateSpot(event, toggle);

        // set local store key to target id or remove key
        _setLocalStorage(toggle.id, !isExpanded ? target.id : !isExpanded);
      }
    }
  };


  var _keyPressed = function (event) {
    // if enter or space key
    if (event.which === 13 || event.which === 32) {
      _toggleClicked(event);
    }
  };


  var _addToggleListeners = function (toggle) {
    // Simpler to mangage here rather than in a global handler (consider hover and blur)

    // Parent of toggle and target
    toggle.addEventListener("focus", _toggleParentClass, false);
    toggle.addEventListener("blur", _toggleParentClass, false);
    toggle.addEventListener("mouseout", _toggleParentClass, false);
    toggle.addEventListener("mouseover", _toggleParentClass, false);

    // Button role
    toggle.addEventListener("click", _toggleClicked, false);
    toggle.addEventListener("keydown", _keyPressed, false);
  };


  var _setUpToggle = function (toggle) {

    // It's not a button so let's fake it properly
    toggle.setAttribute("role", "button");
    toggle.setAttribute("tabindex", 0);

    // If it doesn’t have `aria-expanded`, set it to `false` closed by default
    toggle.hasAttribute("aria-expanded") || toggle.setAttribute("aria-expanded", false);

    // If it doesn’t have an ID, dynamically set it
    toggle.id || toggle.setAttribute("id", attrName + internalId++);

    // Transpose `data-pab` attribute in `aria-controls`
    toggle.setAttribute("aria-controls", toggle.getAttribute(dataAttr));
  };


  var _setUpToggleParent = function (toggle) {
    var parent = toggle.parentElement;
    if (parent) {
      parent.classList.add(attrName + "container");
    }
  }


  var _addToggleSVG = function (toggle) {
    var clone = toggle.cloneNode(true);
    if (!clone.innerHTML.match("svg")) {
      clone.innerHTML = svg_plus + clone.innerHTML;
      _setToggleSvgTitle(clone);
      requestAnimationFrame(function () {
        toggle.parentElement.replaceChild(clone, toggle);
      });
    }
    return clone;
  };


  var _addToggleSpot = function (toggle) {
    var clone = toggle.cloneNode(true);
    if (!clone.innerHTML.match("id=\"spt_")) {
      clone.innerHTML += "<span id=spt_" + internalId + " class=" + spotClass + "></span>";
      clone.spotId = "spt_" + internalId;
      requestAnimationFrame(function () {
        if (toggle.parentElement) {
          toggle.parentElement.replaceChild(clone, toggle);
        } else {
          console.log("no parent?", toggle, toggle.parentElement);
        }
      });
    }
    return clone;
  };


  var _setUpTarget = function (toggle, target) {
    target.setAttribute("aria-hidden", !_isExpanded(toggle));
    target.setAttribute("aria-labelledby", toggle.id);
  };


  var resetTargetsMaxHeight = function () {
    // Iterate over all toggles (elements with the `data-pab` attribute)
    var togglesMap = $("[" + dataAttr + "]").reduce(function (acc, toggle) {

      var target = document.getElementById(toggle.getAttribute(dataAttr));

      if (target) {
        target.style.maxHeight = _getHiddenObjectHeight(target) + "px";
      }
      return true;
    }, {});
  };


  var addToggles = function () {
    // Iterate over all toggles (elements with the `data-pab` attribute)
    var togglesMap = $("[" + dataAttr + "]").reduce(function (acc, toggle) {

      var targetId = toggle.getAttribute(dataAttr);
      var target = document.getElementById(targetId);

      if (target) {
        _setUpToggle(toggle);
        _setUpToggleParent(toggle);
        toggle = _addToggleSVG(toggle);
        toggle = _addToggleSpot(toggle);
        _setUpTarget(toggle, target);
        _addToggleListeners(toggle);
      }
      return true;
    }, {});
    return true;
  };


  addToggles();   
  _openFromLocalStorage();

  // Recalculate target max-heights after window (debounced) is resized
  window.addEventListener('resize', debounce(resetTargetsMaxHeight, 500));

  return {
    addToggles: addToggles
  };

}(svg_plus, useLocalStorage));

              
            
!
999px

Console