I have this sidebar which expands or collapses on a button click. Now I've successfully stored it's state in localStorage and it's working fine except there's a slight issue.
When the page loads and there is no state saved in localStorage, the sidebar collapses for a split second and expands. Expand is supposed to be the default state when there is no state stored in localStorage. I don't want it to collapse first and then expand. I just want the page to load with the sidebar expanded.
I have been trying to solve the issue with my own code. But it didn't work then I combined my code with of of SO's posts. It still doesn't work.
Full Code: Codepen
Here's the code(please note that localStorage won't work in SO):
$('document').ready(function() {
if (typeof window.isMinified === "undefined") {
window.isMinified = false;
}
const body = $('#body');
$("#sidebar-toggler").on("click", function () {
if (window.isMinified === false) {
// localStorage.setItem('menu-closed', !$(body).hasClass("sidebar-minified"));
body.removeClass("sidebar-minified-out").addClass("sidebar-minified");
window.isMinified = true;
} else {
// localStorage.setItem('menu-closed', !$(body).hasClass("sidebar-minified"));
body.removeClass("sidebar-minified").addClass("sidebar-minified-out");
window.isMinified = false;
}
});
const state = // localStorage.getItem('menu-closed');
if (state === null) {
$(body).removeClass('sidebar-minified');
} else {
const closed = state === "true" ? true : false;
if (!closed) {
$(body).removeClass('sidebar-minified');
}
}
});
#body {
background: #fff;
transition: all 0.3s;
}
aside.left-sidebar{
background-color: #2c0963;
height: 100vh;
}
.sidebar-minified-out .left-sidebar {
width: 180px;
transition: width .3s ease-in;
}
.sidebar-minified .left-sidebar {
width: 75px;
transition: width .3s ease-in;
}
.sidebar-toggle {
font-weight: 300;
font-size: 15px;
cursor: pointer;
height: 30px;
position: absolute;
left: 20%;
top: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<body id="body" class="sidebar-minified sidebar-minified-out">
<aside class="left-sidebar"></aside>
<button id="sidebar-toggler" class="sidebar-toggle">Collapse/Expand</button>
</body>
You can minimize the the flashing effect, by telling the browser to repaint just only one time, in one shot. BUT there will be always one initial size for Your sidebar: the size which has been defined inside Your markup.
In my example, I am using two Observers to track the style and size changes. Please, note the initial sidebar width. You may set the initial sidebar width equal to 0, or let it unassigned, or maybe You can style it the same size as Your expanded sidebar, but there will be always an initial repaint.
Finally, I strongly believe You need to remove the two initial classes from the body.
$(function() {
/* avoid SO unsecure operation error */
var storage = (function () {
return {
setItem: function(k,v){try{return localStorage.setItem(k,v)}catch(e){return !1}},
getItem: function(k){try{return localStorage.getItem(k)}catch(e){return null}}
};
})();
log("jQuery DOM Ready");
$("#sidebar-toggler").on("click", function() {
var isMinified = !$("body").hasClass("sidebar-minified-out");
$("body")
.toggleClass("sidebar-minified", !isMinified)
.toggleClass("sidebar-minified-out", isMinified);
storage.setItem('menu-closed', +!isMinified);
});
var closed = +storage.getItem('menu-closed');
log('Closed: ' + !!closed);
$("body")
.addClass(closed ? "sidebar-minified" : "sidebar-minified-out")
.css({"visibility": "visible"});
});
body {
background: #fff;
transition: all 0.3s;
}
aside.left-sidebar{
background-color: #2c0963;
height: 100vh;
}
.sidebar-minified-out .left-sidebar {
width: 180px;
transition: width .3s ease-in;
}
.sidebar-minified .left-sidebar {
width: 75px;
transition: width .3s ease-in;
}
.sidebar-toggle {
font-weight: 300;
font-size: 15px;
cursor: pointer;
height: 30px;
position: absolute;
left: 20%;
top: 0;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Sidebar State</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body style="visibility: hidden;">
<aside class="left-sidebar"></aside>
<button id="sidebar-toggler" class="sidebar-toggle">Collapse/Expand</button>
<div id="log" style="position:absolute;top:0;right:0;padding:1em;"></div>
<script>
/* only debug functions inside this script block */
function log(msg) {
$("<div>").appendTo("#log").text(msg);
}
var mo = new MutationObserver(function (ml){
for(var m of ml) {
if (m.type == 'attributes') log('Body ' + m.attributeName + ' changed');
}
});
mo.observe(document.getElementsByTagName('body')[0], {attributes: true});
var ro = new ResizeObserver(function (rl){
for(var r of rl) {
var w = r.contentRect.width;
if(w<=75 || w>=180) log('Sidebar width: ' + r.contentRect.width);
}
});
ro.observe(document.getElementsByClassName("left-sidebar")[0]);
</script>
</body>
</html>
EDIT:
If You look at the messages logged by the Observers, You will notice that there is always a repaint, as mentioned above.
After reading this solution of Your previous question: Dark mode flickers a white background for a millisecond on reload I believe You can implement Your Sidebar toggler the same way.
Instead of applying the CSS class to the body, You can apply it to the html. Here is the full code:
HTML
<!DOCTYPE html>
<html>
<head>
<script>
/* Render blocking script */
var c = +localStorage.getItem('menu-closed');
document.documentElement.classList.add(c ? 'sidebar-minified' : 'sidebar-minified-out');
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<aside class="left-sidebar"></aside>
<button id="sidebar-toggler" class="sidebar-toggle">Collapse/Expand</button>
</body>
</html>
JS
$(function() {
$("#sidebar-toggler").on("click", function (e) {
var isMinified = !$("html").hasClass("sidebar-minified-out");
$("html")
.toggleClass("sidebar-minified", !isMinified)
.toggleClass("sidebar-minified-out", isMinified);
localStorage.setItem('menu-closed', +!isMinified);
});
});
Your CSS will remain untouched beside of a small change (I just only removed the #body id).
Now, if You compare the Observed changes, You will notice that the second solution, which is using the blocking JS script in head, is showing only the initial Sidebar size, i.e.: the initial repaint is gone:
1st solution 2nd solution
==============================================================
Sidebar width: 601 Closed: false
jQuery DOM Ready Sidebar width: 180
Closed: false jQuery DOM Ready
Body class changed
Body style changed
Sidebar width: 180
(Credits: Roko C. Buljan):
More information:
Here is some documentation about that Observers:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With