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.

wyoung 2018-09-10 08:48 js-hamburger-menu
Commit 90bd66750d539aebc817f906fe8b75dfcf72c4603ec14fbf8b80acf6095773e2
1 file changed +61 -9
--- skins/default/footer.txt
+++ skins/default/footer.txt
@@ -7,23 +7,63 @@
77
<th1>
88
html "<script nonce='$nonce'>"
99
html " (function() { var home='$home'; "
1010
</th1>
1111
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';
1229
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;
1534
}
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);
1851
}
1952
53
+ // Click handler for the hamburger button.
2054
var needSitemapHTML = true;
2155
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);
2565
}
2666
else if (needSitemapHTML) {
2767
// Only get it once per page load: it isn't likely to
2868
// change on us.
2969
var xhr = new XMLHttpRequest();
@@ -30,26 +70,38 @@
3070
xhr.onload = function() {
3171
var doc = xhr.responseXML;
3272
if (doc) {
3373
var sm = doc.querySelector("ul#sitemap");
3474
if (sm && xhr.status == 200) {
75
+ // Got sitemap. Insert it into the drop-down panel.
3576
needSitemapHTML = false;
3677
panel.innerHTML = sm.outerHTML;
3778
if (window.setAllHrefs) {
3879
setAllHrefs(); // don't need anti-robot defense here
3980
}
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();
4193
}
4294
}
4395
// else, can't parse response as HTML or XML
4496
}
4597
xhr.open("GET", home + "/sitemap");
4698
xhr.responseType = "document";
4799
xhr.send();
48100
}
49101
else {
50
- panel.show(); // just show what we built above
102
+ showPanel(); // just show what we built above
51103
}
52104
return false; // prevent browser from acting on <a> click
53105
}
54106
})();
55107
</script>
56108
--- 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

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button