简体   繁体   中英

Sidebar collapses for a second and expands on page load

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, ie: 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:


The debug functions in my first example are used just only to depict the sequence of the resize and restyle events inside the browser.

Here is some documentation about that Observers :

It is the transition from the css that let the sidebar expand. If you remove the transistion you will see that the sidebar ist immediately expanded on page load. So for the first time you should set CSS classes without transitions.

You could disable the transition via Javascript and enable it again for the click event:

jsfiddle demo

First remove the classes in the body tag:

<body id="body" class="">
    <aside class="left-sidebar"></aside>
    <button id="sidebar-toggler" class="sidebar-toggle">Collapse/Expand</button>
</body>

Just a few lines to change in the javascript:

$('document').ready(function() {

    if (typeof window.isMinified === "undefined") {
        window.isMinified = false;
    }

    const body = $('#body');
    $("#sidebar-toggler").on("click", function () {
        
        document.querySelector('.left-sidebar').style.transition = 'inherit';

        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.addClass('sidebar-minified');

    } else {

        const closed = state === "true" ? true : false;

        if (!closed) {
            body.addClass('sidebar-minified-out');
            document.querySelector('.left-sidebar').style.transition = 'none';
        }
        else {
            body.addClass('sidebar-minified');
        }

    }
});

The important changes in above code are two things:

// in the clickevent section: change the transistion to default behaviour
document.querySelector('.left-sidebar').style.transition = 'inherit';

Set the right class depending on state and disable transition:

// ...

if (state === null) {

    body.addClass('sidebar-minified');

} else {

    // ...

    if (!closed) {
        body.addClass('sidebar-minified-out');
        document.querySelector('.left-sidebar').style.transition = 'none';
    }
    else {
        body.addClass('sidebar-minified');
    }

}

*** Update ***

I refactored the code and optimized it a bit. fiddle HTML:

<body id="body">
    <aside class="left-sidebar"></aside>
    <button id="sidebar-toggler" class="sidebar-toggle">Collapse/Expand</button>
</body>

CSS:

#body {
    background: #fff;
    transition: all .3s;
}
aside.left-sidebar {
    background-color: #2c0963;
    height: 100vh;
    width: 75px;
}
.sidebar-minified-out .left-sidebar {
    width: 180px;
}
.sidebar-transitions .left-sidebar {
    transition: width .3s ease-in; 
} 
.sidebar-toggle {
    font-weight: 300;
    font-size: 15px;
    cursor: pointer;
    height: 30px;
    position: absolute;
    left: 20%;
    top: 0;
}

JS:

$('document').ready(function() {

    $("#sidebar-toggler").on("click", function () {
        
        localStorage.setItem('menu-closed', $('#body').hasClass('sidebar-minified-out'));

        $('#body').addClass('sidebar-transitions').toggleClass('sidebar-minified-out');
            
    });

    localStorage.getItem('menu-closed') === "true" ? $('#body').removeClass('sidebar-minified-out') : $('#body').addClass('sidebar-minified-out');

});

How about moving the animation to a separate class lets say

.sidebar-animated{
   transition: width: 0.3s ease-in;
}

and removing it from anywhere else and then adding that class via timeout, so it gets added after the transition is done, you can use useTimeout with 0 seconds, like so,

setTimeout(() => {
    $('aside').addClass('sidebar-animated')    
},0)

Plus CSS uses Specificity to reach its element, so

.sidebar-minified-out .left-sidebar {
  width: 180px;
}

.sidebar-minified .left-sidebar {
  width: 75px;
} 

Should be changed to this.

.sidebar-minified .left-sidebar {
  width: 75px;
} 

.sidebar-minified-out .left-sidebar {
  width: 180px;
}

because when you have both on the same element, it will take the later because they have the same specificity rule, give it a last shot, thats the last resort for me:D.

thats enough to make it work

https://codepen.io/menawer_cpe/pen/qBZbEdw

here is a working example, Note: you have an issue with managing state when the sidebar collapsed at first, but thats something related to how you deal with the state.

Why useTimeout with 0? because it pushes the execution to what is called "event loop" making sure it executes after all javascript normal code is executed.

You can try this:

 $('document').ready(function() { if (window.isMinified === undefined) { window.isMinified = false; } const body = $('#body'); $("#sidebar-toggler").on("click", function() { $('#body.left-sidebar').removeAttr("style"); if (window.isMinified === false) { body.removeClass("sidebar-minified-out").addClass("sidebar-minified"); window.isMinified = true; } else { body.removeClass("sidebar-minified").addClass("sidebar-minified-out"); window.isMinified = false; } }); var firstTime = true; var sidebar = $('#body aside.left-sidebar'); const state =;(null). //localStorage;getItem('menu-closed'). if (state === null) { $(body);removeClass('sidebar-minified'). } else { if (firstTime) { sidebar,css('transition'; 'none'); firstTime = false? } 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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM