Skip to content

Instantly share code, notes, and snippets.

@ryanmead
Last active February 24, 2017 07:28
Show Gist options
  • Select an option

  • Save ryanmead/f5a3d2221f05174992204fa2404db990 to your computer and use it in GitHub Desktop.

Select an option

Save ryanmead/f5a3d2221f05174992204fa2404db990 to your computer and use it in GitHub Desktop.
DL NAVigation

DL NAVigation

for animating a tree structure of nested definition lists

A Pen by ryan on CodePen.

License.

<header class="no-js" id="nav-header">
<button id="top-home">home</button>
</header>
<article class="no-js" id="nav-article">
<dl class="top" id="nav-dl">
<dt>DL NAV</dt>
<dd>Animate a text-light nested &lt;DL&gt; tree
<dl>
<dt>features</dt>
<dd>
<dl>
<dt>good fallback</dt>
<dd>shows an unstyled dl if js disabled</dd>
<dt>easy to use</dt>
<dd>no need to edit js if you add/remove elements</dd>
<dt>variable list depth</dt>
<dd>dls can be nested 2 or 3 deep</dd>
</dl>
</dd>
<dt>how to use</dt>
<dd>instructions
<dl>
<dt><a href="#">offsite links</a></dt>
<dd>use &lt;a&gt; tags like you did <a href="#">last century</a></dd>
<dt>dl structure</dt>
<dd>mind <a href="#">the</a> limitations!</dd>
<dt><a href="//mulletsgalore.com">mullets galore</a></dt>
<dd>too bad<br>you missed it</dd>
<dt>js and scss vars</dt>
<dd>Keep the values at the top of scss and js the same!</dd>
</dl>
</dd>
<dt>limitations</dt>
<dd>
<dl>
<dt>1 to 1 dt:dd</dt>
<dd>must be one and only one dd per dt</dd>
<dt>max depth</dt>
<dd>dls can be nested maximum 3 deep</dd>
</dl>
</dd>
</dl>
</dd>
</dl>
</article>
<footer>
copyright, terms of use, cookie use info, yadda
<button id="bot-home" class="no-js">home</button>
<noscript>
You seem to have JavaScript disabled. This site works fine with JavaScript disabled but looks and behaves wayyyyyy cooler if you enable it!
</noscript>
</footer>
var dlNav = (function() { //main
//must set to same values at top of SCSS
var childDtSize = 140, currentDtSize = 210, parentDtSize = 100;
var currentDtTopMargin = 0.2; //top position of current node
var currentDdVertOffset = 0.58; //relative to height of current dt
var dtSpacing = 0.1; //vertical spacing proportional to childDtSize
var childDdVertOffset = 0.44; //relative to height of child dt
// var dts, article, topHome, botHome; //DOM element references; set on load
var topDl, dts, article, topHome, botHome; //DOM element references; set on load
document.onclick = function(evt, yadda) { //general click handler
if ((evt.target.id==="top-home")||(evt.target.id==="bot-home")) {
setCurrentNode();
return;
}
switch (evt.target.tagName) {
case "DT":
if (evt.target.className==="current-node"&&!isTopNode(evt.target)) {
tapDt(evt.target.parentElement.parentElement.previousElementSibling);
} else { //set clicked dt as current dt
tapDt(evt.target);
}
break;
case "DD": //set associated dt as current dt if click on a dd
if (evt.target.previousElementSibling.className==="current-node"&&!isTopNode(evt.target.previousElementSibling)) {
tapDt(evt.target.previousElementSibling.parentElement.parentElement.previousElementSibling);
} else {
tapDt(evt.target.previousElementSibling);
}
break;
}
}
window.addEventListener("load", init() );
function getChildNodes(node, tagName) {
//return immediate children of passed node; only matching tagName (if given)
var children = new Array();
for(var child in node.childNodes) {
if((node.childNodes[child].nodeType === 1)&&((node.childNodes[child].tagName===tagName)||(!tagName))) {
children.push(node.childNodes[child]);
}
}
return (children.length!==0?children:false);
}
function getIndexInParent(node, onlySiblings) {
//this node is what nth child node of parent node? (for elements of same type if onlySiblings is true)
var peers = (onlySiblings?getChildNodes(node.parentNode, node.tagName):getChildNodes(node.parentNode));
for (i=0;i<peers.length;i++) {
if (peers[i]===node) {
return (i);
};
}
return (-1);
}
function init() {
// dts = document.getElementsByTagName("dt"); //enumerate dt elements
topDl = document.getElementById("nav-dl");
dts = topDl.getElementsByTagName("dt"); //enumerate dt elements
article = document.getElementById("nav-article");
topDl = document.getElementById("nav-dl");
article.className = ""; //remove no-js class from article tag if js active
//header = document.getElementsByTagName("header")[0];
header = document.getElementById("nav-header");
header.className = ""; //remove no-js class from header tag if js active
topHome = document.getElementById("top-home");
botHome = document.getElementById("bot-home");
botHome.className = "";
setCurrentNode(); //set the highest-level node as the current node
}
function isTopNode(node) {
//passed node should be a dt; check whether it's the first dt in the document
return (node===dts[0]);
}
function setCurrentNode(currentDt) {
//display the current node, its parent, its direct children, hide everything else
//all the js style changes that trigger the CSS animations happen here
var childDts, i, j, k;
// var vOffSet = 0; //vertical offset val for child dts
var vOffSet = 0; //vertical offset val for child dts
if (currentDt===undefined) {
//if no dt node passed, set highest-level dt as current
// currentDt = dts[0];
currentDt = dts[0];
}
// //reset classes on all dts
//reset classes on all dts
for (j=0;j<dts.length;j++) {
dts[j].className = "";
}
//set passed dt node as current dt
currentDt.className = "current-node";
//if contained within another dt,
if (currentDt.parentElement.parentElement.previousElementSibling.tagName==="DT") {
currentDt.style.top = -parentDtSize/2 + (currentDtSize*currentDtTopMargin) + "px"; //relative to dd of parent dt
currentDt.nextElementSibling.style.top = -parentDtSize/2 + (currentDtSize*(currentDtTopMargin+currentDdVertOffset)) + "px";
//set parent style on parent dt
currentDt.parentElement.parentElement.previousElementSibling.className = "parent-node";
topHome.disabled = false; //enable the home buttons
botHome.disabled = false;
} else {
//if the topmost node is current
topHome.disabled = true; //disable the home buttons
botHome.disabled = true;
}
// //set upward vertical offset val for child dts to compensate for current dt's position among its siblings
vOffSet = -(currentDtSize*(childDdVertOffset+currentDtTopMargin));
// //set style on child and grandchild dts
childDts = getChildNodes(currentDt.nextElementSibling.childNodes[1], "DT");
for (i=0;i<childDts.length;i++) {
childDts[i].className = "child-node";
childDts[i].style.top = (((i*childDtSize)*(1+dtSpacing))) + vOffSet + "px";
childDts[i].nextElementSibling.style.top = (((i*childDtSize)*(1+dtSpacing))+(childDtSize*childDdVertOffset)) + vOffSet + "px";
if (childDts[i].nextElementSibling.getElementsByTagName("dt").length===0) { //set leaf node style if no children
childDts[i].className += " leaf-node";
}
}
//set article height
article.style["height"] = (childDts.length*((childDtSize)*(1+dtSpacing))) + "px";
}
function tapLeafDt(tappedLeafDt) {
//if a leaf node contains only one <a> tag, follow it when click anywhere on leaf node
var dtLinks, ddLinks, ret;
//get all anchor elements that are direct children of the dt or dd (more deeply nested <a> tags not followed)
dtLinks = getChildNodes(tappedLeafDt, "A");
ddLinks = getChildNodes(tappedLeafDt.nextElementSibling, "A");
// if ((isNaN(dtLinks.length)?0:dtLinks.length), (isNaN(ddLinks.length)?0:ddLinks.length), ((isNaN(dtLinks.length)?0:dtLinks.length)+(isNaN(ddLinks.length)?0:ddLinks.length)===1)) {
if ((isNaN(dtLinks.length)?0:dtLinks.length)+(isNaN(ddLinks.length)?0:ddLinks.length)===1) {
if (!isNaN(dtLinks.length)) {
ret = dtLinks[0].href;
} else if (!isNaN(ddLinks.length)) {
ret = ddLinks[0].href;
} else {
console.log("couldnt find link");
}
console.log (ret);
location.href = ret;
}
}
function tapDt(tappedDt) {
if (!(tappedDt.nextElementSibling.getElementsByTagName("dt").length===0)) {
setCurrentNode(tappedDt);
} else {
tapLeafDt(tappedDt);
}
}
}());
//@import url('https://fonts.googleapis.com/css?family=Open+Sans');
@import url('https://fonts.googleapis.com/css?family=Lato');
//must set to same values at top of js
$childDtSize: 140px;
$currentDtSize: 210px;
$parentDtSize: 100px;
$currentDtTopMargin: 0.2;
$currentDdVertOffset: 0.48;
$wider-than-phone: "(min-width: 600px)";
$orange: #fc5300;
$clickableNode: $orange;
$offWhite: #ffffff;
$veryVeryDarkGray: #222222;
$bgGrey: #666666;
$relborderRadius: 1;
@mixin border-radius($radius) {
-webkit-border-radius: $radius;
-moz-border-radius: $radius;
-ms-border-radius: $radius;
border-radius: $radius;
}
@mixin shown-dt($diameter, $heightFromTop) { //governs appearance of visible nodes
@include border-radius($diameter*$relborderRadius);
display:block;
color:$offWhite;
text-align:left;
padding-left:($diameter*0.1);
width:($diameter*0.9);
padding-top:($diameter*$heightFromTop);
height:($diameter*(1-$heightFromTop));
}
@mixin traverse-transition($traverseTime) { //when moving between nodes
transition-property: padding-top, height, width, top, left, background-color, font-size, border-radius;
transition-duration: $traverseTime, $traverseTime, $traverseTime, $traverseTime, $traverseTime, $traverseTime, $traverseTime, $traverseTime;
}
body {
margin:0;
color:$veryVeryDarkGray;
background-color:$offWhite*0.9;
font-family:'Lato',arial,sans,sans-serif;
}
a {
color:inherit;
&:hover {
text-decoration: none;
}
}
header, footer {
margin:0.5em;
}
noscript {
display:block;
}
button {
background-color:$clickableNode;
color:$offWhite;
border:1px solid $clickableNode*2;
border-radius:0.4em;
padding:0.2em;
cursor:pointer;
&:disabled {
background-color:$bgGrey*1.4;
border-color:$bgGrey*2;
color:$bgGrey*2;
cursor:default;
}
transition-property: background-color, border-color, color;
transition-duration: 0.8s, 0.8s, 0.8s;
}
#nav-article {
position:relative;
padding:20px;
background-color:$offWhite*0.83;
transition-property: height;
transition-duration: 0.8s;
& dl {
position:absolute;
top:0;
left:0;
margin:0;
}
& dl.top {
left:50%;
margin-left:-(($currentDtSize+$childDtSize)/2);
}
& dt {
position:absolute;
@include traverse-transition(1s);
font-weight:600;
visibility:hidden;
margin:0;
top:0;
left:0;
}
& dd {
font-size:1em;
position:absolute;
@include traverse-transition(1s);
visibility:hidden;
margin:0;
z-index:100;
text-align:left;
color:$offWhite;
width:$childDtSize;
top:0;
left:0;
padding-left:0.5em;
margin-right:1em;
}
& dt.current-node {
visibility:visible;
@include shown-dt($currentDtSize, 0.4);
background-color:$bgGrey;
border: 1px solid $bgGrey*1.4;
top:$currentDtSize*$currentDtTopMargin;
left:$currentDtSize*0.1;
z-index:40;
font-size:2em;
}
& dt.current-node + dd {
visibility:visible;
top:$currentDtSize*($currentDtTopMargin+$currentDdVertOffset+0.1);
//issue here
left:$currentDtSize*0.15;
width:$currentDtSize*0.8;
}
& dt.parent-node {
visibility:visible;
@include shown-dt($parentDtSize, 0.3);
cursor:pointer;
background-color:$clickableNode;
border: 1px solid $clickableNode*1.1;
position:absolute;
top:0;
left:0;
@media #{$wider-than-phone} {
left: -$parentDtSize/4;
}
}
& dt.parent-node + dd {
top:$childDtSize*0.4;
left:0;
cursor:pointer;
}
& dt.child-node {
left:$childDtSize;
@media #{$wider-than-phone} {
left: $parentDtSize*2;
}
@include shown-dt($childDtSize, 0.3);
background-color:$clickableNode;
cursor:pointer;
border:1px solid $clickableNode*2;
visibility:visible;
z-index:150;
}
& dt.child-node + dd {
left:$childDtSize;
@media #{$wider-than-phone} {
left: $parentDtSize*2;
}
visibility:visible;
display:inline-block;
z-index:200;
cursor:pointer;
}
& dt.leaf-node, dt.leaf-node + dd {
cursor:default;
}
}
article.no-js { //if js disabled
& * {
position:relative;
display:default;
visibility:visible;
}
& dt, & dd {
color:$veryVeryDarkGray;
}
& dd {
padding-left: 2em;
}
}
header.no-js {
display:none;
}
button.no-js {
display:none;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment