for animating a tree structure of nested definition lists
Last active
February 24, 2017 07:28
-
-
Save ryanmead/f5a3d2221f05174992204fa2404db990 to your computer and use it in GitHub Desktop.
DL NAVigation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <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 <DL> 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 <a> 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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } | |
| }()); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //@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