Fossil SCM
Using CSS transitions to mimic jQuery's slideUp/Down() transitions. This probably restricts browser compatibility still further above the XHR issue noted in the earlier checkin on this branch. According to MDN, we're probably restricted to IE 10+ with this, and maybe not even that due to not using vendor-specific extensions for the transitional browser versions.
Commit
90bd66750d539aebc817f906fe8b75dfcf72c4603ec14fbf8b80acf6095773e2
Parent
113ba3d9deb2e77…
1 file changed
+61
-9
+61
-9
| --- skins/default/footer.txt | ||
| +++ skins/default/footer.txt | ||
| @@ -7,23 +7,63 @@ | ||
| 7 | 7 | <th1> |
| 8 | 8 | html "<script nonce='$nonce'>" |
| 9 | 9 | html " (function() { var home='$home'; " |
| 10 | 10 | </th1> |
| 11 | 11 | var panel = document.getElementById("hbdrop"); |
| 12 | + var panelBorder = panel.style.border; | |
| 13 | + | |
| 14 | + // Calculate panel height despite its being hidden at call time. | |
| 15 | + // Based on https://stackoverflow.com/a/29047447/142454 | |
| 16 | + var panelHeight; // computed on sitemap load | |
| 17 | + function calculatePanelHeight() { | |
| 18 | + // Get initial panel styles so we can restore them below. | |
| 19 | + var es = window.getComputedStyle(panel), | |
| 20 | + edis = es.display, | |
| 21 | + epos = es.position, | |
| 22 | + evis = es.visibility; | |
| 23 | + | |
| 24 | + // Restyle the panel so we can measure its height while invisible. | |
| 25 | + panel.style.visibility = 'hidden'; | |
| 26 | + panel.style.position = 'absolute'; | |
| 27 | + panel.style.display = 'block'; | |
| 28 | + panelHeight = panel.offsetHeight + 'px'; | |
| 12 | 29 | |
| 13 | - panel.onclick = panel.hide = function() { | |
| 14 | - panel.style.display = 'none'; | |
| 30 | + // Revert styles now that job is done. | |
| 31 | + panel.style.display = edis; | |
| 32 | + panel.style.position = epos; | |
| 33 | + panel.style.visibility = evis; | |
| 15 | 34 | } |
| 16 | - panel.show = function() { | |
| 17 | - panel.style.display = 'block'; | |
| 35 | + | |
| 36 | + // Show the panel by changing the panel height, which kicks off the | |
| 37 | + // slide-open/closed transition set up in the XHR onload handler. | |
| 38 | + // | |
| 39 | + // Schedule the change for a near-future time in case this is the | |
| 40 | + // first call, where the div was initially invisible. That causes | |
| 41 | + // the browser to consider the height change as part of the same | |
| 42 | + // state change as the visibility change, so it doesn't see a state | |
| 43 | + // *transition*, hence never kicks off the *CSS* transition: | |
| 44 | + // | |
| 45 | + // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions#JavaScript_examples | |
| 46 | + function showPanel() { | |
| 47 | + setTimeout(function() { | |
| 48 | + panel.style.maxHeight = panelHeight; | |
| 49 | + panel.style.border = panelBorder; | |
| 50 | + }, 10); | |
| 18 | 51 | } |
| 19 | 52 | |
| 53 | + // Click handler for the hamburger button. | |
| 20 | 54 | var needSitemapHTML = true; |
| 21 | 55 | document.querySelector("div.mainmenu > a").onclick = function() { |
| 22 | - if (panel.style.display == 'block') { | |
| 23 | - // Hamburger button clicked a second time. | |
| 24 | - panel.hide(); | |
| 56 | + if (panel.style.maxHeight == panelHeight) { | |
| 57 | + // Hamburger button clicked while panel visible. Trigger the | |
| 58 | + // transition back to hidden state. | |
| 59 | + panel.style.maxHeight = '0'; | |
| 60 | + setTimeout(function() { | |
| 61 | + // Browsers show a 1px high line when maxHeight == 0, so | |
| 62 | + // temporarily hide the borders while hidden. | |
| 63 | + panel.style.border = 'none'; | |
| 64 | + }, 300); | |
| 25 | 65 | } |
| 26 | 66 | else if (needSitemapHTML) { |
| 27 | 67 | // Only get it once per page load: it isn't likely to |
| 28 | 68 | // change on us. |
| 29 | 69 | var xhr = new XMLHttpRequest(); |
| @@ -30,26 +70,38 @@ | ||
| 30 | 70 | xhr.onload = function() { |
| 31 | 71 | var doc = xhr.responseXML; |
| 32 | 72 | if (doc) { |
| 33 | 73 | var sm = doc.querySelector("ul#sitemap"); |
| 34 | 74 | if (sm && xhr.status == 200) { |
| 75 | + // Got sitemap. Insert it into the drop-down panel. | |
| 35 | 76 | needSitemapHTML = false; |
| 36 | 77 | panel.innerHTML = sm.outerHTML; |
| 37 | 78 | if (window.setAllHrefs) { |
| 38 | 79 | setAllHrefs(); // don't need anti-robot defense here |
| 39 | 80 | } |
| 40 | - panel.show(); | |
| 81 | + | |
| 82 | + // Set up the CSS transition to animate the panel open and | |
| 83 | + // closed. Only needs to be done once per page load. | |
| 84 | + // Based on https://stackoverflow.com/a/29047447/142454 | |
| 85 | + calculatePanelHeight(); | |
| 86 | + panel.style.transition = 'max-height 0.5s ease-in-out'; | |
| 87 | + panel.style.overflowY = 'hidden'; | |
| 88 | + panel.style.maxHeight = '0'; | |
| 89 | + panel.style.display = 'block'; | |
| 90 | + | |
| 91 | + // Kick off the transition | |
| 92 | + showPanel(); | |
| 41 | 93 | } |
| 42 | 94 | } |
| 43 | 95 | // else, can't parse response as HTML or XML |
| 44 | 96 | } |
| 45 | 97 | xhr.open("GET", home + "/sitemap"); |
| 46 | 98 | xhr.responseType = "document"; |
| 47 | 99 | xhr.send(); |
| 48 | 100 | } |
| 49 | 101 | else { |
| 50 | - panel.show(); // just show what we built above | |
| 102 | + showPanel(); // just show what we built above | |
| 51 | 103 | } |
| 52 | 104 | return false; // prevent browser from acting on <a> click |
| 53 | 105 | } |
| 54 | 106 | })(); |
| 55 | 107 | </script> |
| 56 | 108 |
| --- skins/default/footer.txt | |
| +++ skins/default/footer.txt | |
| @@ -7,23 +7,63 @@ | |
| 7 | <th1> |
| 8 | html "<script nonce='$nonce'>" |
| 9 | html " (function() { var home='$home'; " |
| 10 | </th1> |
| 11 | var panel = document.getElementById("hbdrop"); |
| 12 | |
| 13 | panel.onclick = panel.hide = function() { |
| 14 | panel.style.display = 'none'; |
| 15 | } |
| 16 | panel.show = function() { |
| 17 | panel.style.display = 'block'; |
| 18 | } |
| 19 | |
| 20 | var needSitemapHTML = true; |
| 21 | document.querySelector("div.mainmenu > a").onclick = function() { |
| 22 | if (panel.style.display == 'block') { |
| 23 | // Hamburger button clicked a second time. |
| 24 | panel.hide(); |
| 25 | } |
| 26 | else if (needSitemapHTML) { |
| 27 | // Only get it once per page load: it isn't likely to |
| 28 | // change on us. |
| 29 | var xhr = new XMLHttpRequest(); |
| @@ -30,26 +70,38 @@ | |
| 30 | xhr.onload = function() { |
| 31 | var doc = xhr.responseXML; |
| 32 | if (doc) { |
| 33 | var sm = doc.querySelector("ul#sitemap"); |
| 34 | if (sm && xhr.status == 200) { |
| 35 | needSitemapHTML = false; |
| 36 | panel.innerHTML = sm.outerHTML; |
| 37 | if (window.setAllHrefs) { |
| 38 | setAllHrefs(); // don't need anti-robot defense here |
| 39 | } |
| 40 | panel.show(); |
| 41 | } |
| 42 | } |
| 43 | // else, can't parse response as HTML or XML |
| 44 | } |
| 45 | xhr.open("GET", home + "/sitemap"); |
| 46 | xhr.responseType = "document"; |
| 47 | xhr.send(); |
| 48 | } |
| 49 | else { |
| 50 | panel.show(); // just show what we built above |
| 51 | } |
| 52 | return false; // prevent browser from acting on <a> click |
| 53 | } |
| 54 | })(); |
| 55 | </script> |
| 56 |
| --- skins/default/footer.txt | |
| +++ skins/default/footer.txt | |
| @@ -7,23 +7,63 @@ | |
| 7 | <th1> |
| 8 | html "<script nonce='$nonce'>" |
| 9 | html " (function() { var home='$home'; " |
| 10 | </th1> |
| 11 | var panel = document.getElementById("hbdrop"); |
| 12 | var panelBorder = panel.style.border; |
| 13 | |
| 14 | // Calculate panel height despite its being hidden at call time. |
| 15 | // Based on https://stackoverflow.com/a/29047447/142454 |
| 16 | var panelHeight; // computed on sitemap load |
| 17 | function calculatePanelHeight() { |
| 18 | // Get initial panel styles so we can restore them below. |
| 19 | var es = window.getComputedStyle(panel), |
| 20 | edis = es.display, |
| 21 | epos = es.position, |
| 22 | evis = es.visibility; |
| 23 | |
| 24 | // Restyle the panel so we can measure its height while invisible. |
| 25 | panel.style.visibility = 'hidden'; |
| 26 | panel.style.position = 'absolute'; |
| 27 | panel.style.display = 'block'; |
| 28 | panelHeight = panel.offsetHeight + 'px'; |
| 29 | |
| 30 | // Revert styles now that job is done. |
| 31 | panel.style.display = edis; |
| 32 | panel.style.position = epos; |
| 33 | panel.style.visibility = evis; |
| 34 | } |
| 35 | |
| 36 | // Show the panel by changing the panel height, which kicks off the |
| 37 | // slide-open/closed transition set up in the XHR onload handler. |
| 38 | // |
| 39 | // Schedule the change for a near-future time in case this is the |
| 40 | // first call, where the div was initially invisible. That causes |
| 41 | // the browser to consider the height change as part of the same |
| 42 | // state change as the visibility change, so it doesn't see a state |
| 43 | // *transition*, hence never kicks off the *CSS* transition: |
| 44 | // |
| 45 | // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions#JavaScript_examples |
| 46 | function showPanel() { |
| 47 | setTimeout(function() { |
| 48 | panel.style.maxHeight = panelHeight; |
| 49 | panel.style.border = panelBorder; |
| 50 | }, 10); |
| 51 | } |
| 52 | |
| 53 | // Click handler for the hamburger button. |
| 54 | var needSitemapHTML = true; |
| 55 | document.querySelector("div.mainmenu > a").onclick = function() { |
| 56 | if (panel.style.maxHeight == panelHeight) { |
| 57 | // Hamburger button clicked while panel visible. Trigger the |
| 58 | // transition back to hidden state. |
| 59 | panel.style.maxHeight = '0'; |
| 60 | setTimeout(function() { |
| 61 | // Browsers show a 1px high line when maxHeight == 0, so |
| 62 | // temporarily hide the borders while hidden. |
| 63 | panel.style.border = 'none'; |
| 64 | }, 300); |
| 65 | } |
| 66 | else if (needSitemapHTML) { |
| 67 | // Only get it once per page load: it isn't likely to |
| 68 | // change on us. |
| 69 | var xhr = new XMLHttpRequest(); |
| @@ -30,26 +70,38 @@ | |
| 70 | xhr.onload = function() { |
| 71 | var doc = xhr.responseXML; |
| 72 | if (doc) { |
| 73 | var sm = doc.querySelector("ul#sitemap"); |
| 74 | if (sm && xhr.status == 200) { |
| 75 | // Got sitemap. Insert it into the drop-down panel. |
| 76 | needSitemapHTML = false; |
| 77 | panel.innerHTML = sm.outerHTML; |
| 78 | if (window.setAllHrefs) { |
| 79 | setAllHrefs(); // don't need anti-robot defense here |
| 80 | } |
| 81 | |
| 82 | // Set up the CSS transition to animate the panel open and |
| 83 | // closed. Only needs to be done once per page load. |
| 84 | // Based on https://stackoverflow.com/a/29047447/142454 |
| 85 | calculatePanelHeight(); |
| 86 | panel.style.transition = 'max-height 0.5s ease-in-out'; |
| 87 | panel.style.overflowY = 'hidden'; |
| 88 | panel.style.maxHeight = '0'; |
| 89 | panel.style.display = 'block'; |
| 90 | |
| 91 | // Kick off the transition |
| 92 | showPanel(); |
| 93 | } |
| 94 | } |
| 95 | // else, can't parse response as HTML or XML |
| 96 | } |
| 97 | xhr.open("GET", home + "/sitemap"); |
| 98 | xhr.responseType = "document"; |
| 99 | xhr.send(); |
| 100 | } |
| 101 | else { |
| 102 | showPanel(); // just show what we built above |
| 103 | } |
| 104 | return false; // prevent browser from acting on <a> click |
| 105 | } |
| 106 | })(); |
| 107 | </script> |
| 108 |