Offcanvas dismiss button does not work when instance initialised via Javascript

Question:

I have offcanvas as part of the page layout. It does not show by default, but I want it to always show on large screens. On small screens it should have a button to dismiss it. Another button to show the offcanvas is placed on the menu panel underneath it.

I’m using JavaScript on page load and on page resize to show the offcanvas when if screen is large. If the user then scales the screen down, the offcanvas should remain visible until dismissed.

The issue is that the dismiss button works on the offcanvas only if it has not been initialised via JS. I.e. if opened on a small screen, the offcanvas is not visible and the two bottons can be used to toggle back and forth. But once the offcanvas has been triggerd via JS on a large screen, the dismiss button no longer works when the screen is downsized. I’m struggling to figure out if it has lost an EventListener and how to add it back, if so.

Layout:

<!-- Button to show offcanvas (positioned underneath it) --> 
<div>
    <button class="btn" type="button" id="off-canvas-body-toggle" data-bs-toggle="offcanvas" data-bs-target="#floating-menu">
        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#422543" class="bi bi-arrow-bar-right" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M6 8a.5.5 0 0 0 .5.5h5.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3a.5.5 0 0 0 0-.708l-3-3a.5.5 0 0 0-.708.708L12.293 7.5H6.5A.5.5 0 0 0 6 8Zm-2.5 7a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 1 0v13a.5.5 0 0 1-.5.5Z" />
        </svg>
    </button>
</div>

<!-- Offcanvas -->
<div class="offcanvas offcanvas-start" data-bs-scroll="true" data-bs-backdrop="false" tabindex="-1" id="floating-menu">
    
    <!-- Bootstrap classes d-xl-none d-lg-none hides the div with dismiss button on large screen -->
    <!-- But it becomes visible when the screen is downsized -->
    <div class="offcanvas-header d-xl-none d-lg-none">
        <!-- Dismiss button that does not work corectly -->
        <button type="button" class="btn" id="off-canvas-body-dismiss" data-bs-dismiss="offcanvas">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#422543" class="bi bi-arrow-bar-left" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M12.5 15a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 1 0v13a.5.5 0 0 1-.5.5ZM10 8a.5.5 0 0 1-.5.5H3.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L3.707 7.5H9.5a.5.5 0 0 1 .5.5Z" /></svg>
        </button>
    </div>

    <div class="offcanvas-body">
    <!-- This contains an accordion and works fine -->
    </div>
</div>

JS:

// Once the HTML has loaded
document.addEventListener("DOMContentLoaded", function () {

// If on large screen, show offcanvas upon page load
adjust_layout();

// On resize - if large screen then always show offcanvas
addEventListener("resize", adjust_layout);
}

function adjust_layout() {
  // Get the Div that contains the offcanvas
  const menu = document.getElementById("floating-menu");
  var bsOffcanvas = new bootstrap.Offcanvas(menu);

  // If large screen - show the offcanvas
  let width = window.innerWidth;
  if (width >= 992) {
    bsOffcanvas.show();
  }
}
Asked By: VikSil

||

Answers:

As you’re creating a new instance on every resize, most likely newly created JavaScript instance interferes somehow with data attribute instance (or I guess dismiss button doesn’t work if .show method wasn’t called on newly created instance…).

So, use getOrCreateInstance method instead, to get the same offcanvas instance for that DOM element:

instead of:

var bsOffcanvas = new bootstrap.Offcanvas(menu);

use:

const bsOffcanvas = bootstrap.Offcanvas.getOrCreateInstance(menu);

or shorter:

function adjust_layout() {
  // Get the Div that contains the offcanvas
  const bsOffcanvas = bootstrap.Offcanvas.getOrCreateInstance('#floating-menu');
  //...

See:

getOrCreateInstance Static method which allows you to get the
offcanvas instance associated with a DOM element, or create a new one
in case it wasn’t initialized.

https://getbootstrap.com/docs/5.3/components/offcanvas/#methods

Also, your code seems to work if you create the instance only once, and not on every resize, i.e. if you move it outside the adjust_layout function like so, but I’d use getOrCreateInstance method instead to make sure both data and JS parts work as expected, so here’s the final suggested code (in any case, you should create the instance only once, not on every resize):

// Get the Div that contains the offcanvas
const bsOffcanvas = bootstrap.Offcanvas.getOrCreateInstance('#floating-menu');

// Once the HTML has loaded
document.addEventListener("DOMContentLoaded", function() {

  // If on large screen, show offcanvas upon page load
  adjust_layout();

  // On resize - if large screen then always show offcanvas
  window.addEventListener("resize", adjust_layout);

});

function adjust_layout() {

  // If large screen - show the offcanvas
  let width = window.innerWidth;
  if (width >= 992) {
    bsOffcanvas.show();
  }
}
Answered By: traynor