0

I was searching for a way to scroll to an element in order to trigger an event. I noticed a lot of the examples' scrollTo(), scrollBy(), etc. wouldn't work (except scrollByView() when called via on:click in a button or scrolling using the classic <a href="#element"></a>).

I tested with something as simple as this in my .svelte file (result -> nothing happened, no errors either) to see if scrolling even worked:

<script>
  let y;
</script>

<svelte:window bind:scrollY={y} on:scroll={console.log("SCROLL Y VALUE: " + y)}/>

Did some more digging and I ran into JavaScript scrollTo method does nothing, after which I inspected my page once more and I noticed in the DevTools that a scroll and overflow boxes would appear when clicking on some of the elements.

Then it clicked... of course I can't scroll. There's no page TO scroll. All I'm scrolling is the overflow content while the window content's height technically remained the same.

The following code I found here, confirms that the <main></main> section is causing the overflow (naturally).

document.querySelectorAll('*').forEach(el => {
  if (el.offsetHeight > document.documentElement.offsetHeight) {
      console.log('Found the worst element ever: ', el);
  }
});

(I'm using the navbar menu for desktop + drawer for mobile from DaisyUI in my Svelte website.)

Header.svelte

<div class="rounded-lg shadow bg-base-200 drawer h-52"> <!-- It's 'h-screen' in my case. Though, even without either, I'm still technically scrolling the overflowed content. -->
  <input id="my-drawer-3" type="checkbox" class="drawer-toggle"> 
  <div class="flex flex-col drawer-content">
    <div class="w-full navbar bg-base-300">
      <div class="flex-none lg:hidden">
        <label for="my-drawer-3" class="btn btn-square btn-ghost">
          <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="inline-block w-6 h-6 stroke-current">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
          </svg>
        </label>
      </div> 
      <div class="flex-1 px-2 mx-2">
        <span>
              Change screen size to show/hide menu
            </span>
      </div> 
      <div class="flex-none hidden lg:block">
        <ul class="menu horizontal">
          <li>
            <a class="rounded-btn">Item 1</a>
          </li> 
          <li>
            <a class="rounded-btn">Item 2</a>
          </li>
        </ul>
      </div>
    </div>
  </div> 

  <!-- THIS IS WHERE CONTENT MUST GO IN ORDER FOR THE DRAWER TO APPEAR OVER IT WHEN OPENED -->
  <slot></slot>

  <div class="drawer-side">
    <label for="my-drawer-3" class="drawer-overlay"></label> 
    <ul class="p-4 overflow-y-auto menu w-80 bg-base-100">
      <li>
        <a>Item 1</a>
      </li> 
      <li>
        <a>Item 2</a>
      </li>
    </ul>
  </div>
</div>

__layout.svelte:

<script>
  import Header from "$lib/Header.svelte"
</script>

<Header>
  <main>
    <!-- index.svelte goes here -->
    <slot></slot>
  </main>

<!-- DaisyUI footer -->
<footer class="p-10 footer bg-neutral text-neutral-content">
  <div>
    <svg width="50" height="50" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" class="fill-current">
      <path d="M22.672 15.226l-2.432.811.841 2.515c.33 1.019-.209 2.127-1.23 2.456-1.15.325-2.148-.321-2.463-1.226l-.84-2.518-5.013 1.677.84 2.517c.391 1.203-.434 2.542-1.831 2.542-.88 0-1.601-.564-1.86-1.314l-.842-2.516-2.431.809c-1.135.328-2.145-.317-2.463-1.229-.329-1.018.211-2.127 1.231-2.456l2.432-.809-1.621-4.823-2.432.808c-1.355.384-2.558-.59-2.558-1.839 0-.817.509-1.582 1.327-1.846l2.433-.809-.842-2.515c-.33-1.02.211-2.129 1.232-2.458 1.02-.329 2.13.209 2.461 1.229l.842 2.515 5.011-1.677-.839-2.517c-.403-1.238.484-2.553 1.843-2.553.819 0 1.585.509 1.85 1.326l.841 2.517 2.431-.81c1.02-.33 2.131.211 2.461 1.229.332 1.018-.21 2.126-1.23 2.456l-2.433.809 1.622 4.823 2.433-.809c1.242-.401 2.557.484 2.557 1.838 0 .819-.51 1.583-1.328 1.847m-8.992-6.428l-5.01 1.675 1.619 4.828 5.011-1.674-1.62-4.829z"></path>
    </svg> 
    <p>ACME Industries Ltd.
      <br>Providing reliable tech since 1992
    </p>
  </div> 
  <div>
    <span class="footer-title">Social</span> 
    <div class="grid grid-flow-col gap-4">
      <a>
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="fill-current">
          <path d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z"></path>
        </svg>
      </a> 
      <a>
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="fill-current">
          <path d="M19.615 3.184c-3.604-.246-11.631-.245-15.23 0-3.897.266-4.356 2.62-4.385 8.816.029 6.185.484 8.549 4.385 8.816 3.6.245 11.626.246 15.23 0 3.897-.266 4.356-2.62 4.385-8.816-.029-6.185-.484-8.549-4.385-8.816zm-10.615 12.816v-8l8 3.993-8 4.007z"></path>
        </svg>
      </a> 
      <a>
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="fill-current">
          <path d="M9 8h-3v4h3v12h5v-12h3.642l.358-4h-4v-1.667c0-.955.192-1.333 1.115-1.333h2.885v-5h-3.808c-3.596 0-5.192 1.583-5.192 4.615v3.385z"></path>
        </svg>
      </a>
    </div>
  </div>
</footer>

</Header>

<style>
</style>

index.svelte:

<script>
let y;

function scrolling() {
    console.log("SCROLL Y VALUE: " + y);
}
</script>

<svelte:head>
    <title>Page Title</title>
</svelte:head>

<svelte:window bind:scrollY={y} on:scroll="{scrolling}"/>

<div class="hero min-h-screen bg-base-200">
  <div class="text-center hero-content">
    <div class="max-w-md">
      <h1 class="mb-5 text-5xl font-bold">
            Hello there
          </h1> 
      <p class="mb-5">
            Provident cupiditate voluptatem et in. Quaerat fugiat ut assumenda excepturi exercitationem quasi. In deleniti eaque aut repudiandae et a id nisi.
          </p> 
      <button class="btn btn-primary">Get Started</button>
    </div>
  </div>
</div>

<div class="hero min-h-screen bg-base-200">
  <div class="text-center hero-content">
    <div class="max-w-md">
      <h1 class="mb-5 text-5xl font-bold">
            Hello there
          </h1> 
      <p class="mb-5">
            Provident cupiditate voluptatem et in. Quaerat fugiat ut assumenda excepturi exercitationem quasi. In deleniti eaque aut repudiandae et a id nisi.
          </p> 
      <button class="btn btn-primary">Get Started</button>
    </div>
  </div>
</div>

If I put the <slot></slot> outside of the <Header></Header> inside the __layout.svelte scrolling works as expected, but then the navbar and drawer sections don't.

Question:

How can I restructure my Svelte components in order for the drawer to remain above the content (i.e. have a higher z-index), for its height to be the same as the screen and still be able to scroll when it's not showing (e.g. on desktop)?

P.S. The examples of drawers I found always seemed to work in the same way as the DaisyUI one, which is to place your content inside it.

Doombringer
  • 596
  • 4
  • 19

1 Answers1

0

Solution Logic

The idea behind the solution is to separate the navbar and drawer into different components and use store writable() (global variable to which a component can "subscribe to" in order to listen for changes and, in this case, modify as well) in conjuction with the class directive to apply different style classes to different elements for when opening and closing the drawer.

[The follwoing solution can also be implemented in other frameworks such as VueJS or even using vanilla JavaScript.]

Tools:

  • Svelte / SvelteKit (in this case)
  • TailWindCSS
  • DaisyUI (requires TailWindCSS)

Common problems

I tried a few other possible solutions (in some of them without even using an overlay element) but I always ended up running into one of the following problems, among others:

  • The drawer component would go behind the navigation bar when closing.
  • The element marked as overlay's opacity or background-color would not trigger when transitioning upon closing (it would just skip to "0" because of the way the drawer container was being hidden).
  • The drawer and the element marked as drawer container had to both have opacity reduction upon closing transition. (This ended up looking fine, which is the first solution I came up with, but I wanted to get closer to the original visual transitions of DaisyUI.)
  • Sometimes the transitions would only work properly for either a "sticky" navigation bar or a normal one, but never both at the same time.

Important Notes

Code

stores.js:

import { writable } from 'svelte/store';

// Writable variable which gets read and modified by both components.
export const toggleCustomDrawer = writable(false);

index.svelte:

<script>
</script>

<div class="hero min-h-screen bg-base-200">
  <div class="flex-col hero-content lg:flex-row-reverse">
    <img src="https://picsum.photos/id/1005/600/600" class="max-w-sm rounded-lg shadow-2xl" alt="Dude with glasses"> 
    <div>
      <h1 class="mb-5 text-5xl font-bold">
            Hello there
          </h1> 
      <p class="mb-5">
            Provident cupiditate voluptatem et in. Quaerat fugiat ut assumenda excepturi exercitationem quasi. In deleniti eaque aut repudiandae et a id nisi.
          </p> 
      <button class="btn btn-primary">Get Started</button>
    </div>
  </div>
</div>

<div class="hero min-h-screen" style="background-image: url(&quot;https://picsum.photos/id/1005/1600/1400&quot;);">
  <div class="hero-overlay bg-opacity-60"></div> 
  <div class="text-center hero-content text-neutral-content">
    <div class="max-w-md">
      <h1 class="mb-5 text-5xl font-bold">
            Hello there
          </h1> 
      <p class="mb-5">
            Provident cupiditate voluptatem et in. Quaerat fugiat ut assumenda excepturi exercitationem quasi. In deleniti eaque aut repudiandae et a id nisi.
          </p> 
      <button class="btn btn-primary">Get Started</button>
    </div>
  </div>
</div>

<style>
</style>

CustomHeader.svelte:

<script>
    import { toggleCustomDrawer } from '../stores/stores.js';

    let customDrawerOn;

    // Subscribe to the 'writable()' to listen for changes / be able to change the value.
    toggleCustomDrawer.subscribe(value => {
      customDrawerOn = value;
    });

    // Change the value of the 'writable()'.
    function toggleCustomDrawerOnOff() {
      toggleCustomDrawer.update(value => customDrawerOn ? false : true);
      console.log("[CustomHeader.svelte] toggleDrawerOnOff(): <<< " + customDrawerOn + " >>>");
    }
</script>

<!-- Original navbar used -> 'start/center/end' (the last one in the list as of writing this)
     See -> https://daisyui.com/components/navbar
-->
<div class="navigation-bar-container fixed top-0 w-full">
  <div class="navbar mb-2 shadow-lg bg-primary text-neutral-content">
    <div class="px-2 mx-2 navbar-start">
      <button class="flex-none lg:hidden btn btn-square btn-ghost" on:click={toggleCustomDrawerOnOff}>
        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="inline-block w-6 h-6 stroke-current">           
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>               
        </svg>
      </button>
      <span class="text-lg font-bold">daisyUI</span>
    </div> 
    <div class="hidden px-2 mx-2 navbar-center lg:flex">
      <div class="flex items-stretch">
        <a class="btn btn-ghost btn-sm rounded-btn" href="asd">Home</a> 
        <a class="btn btn-ghost btn-sm rounded-btn" href="asd">Portfolio</a> 
        <a class="btn btn-ghost btn-sm rounded-btn" href="asd">About</a> 
        <a class="btn btn-ghost btn-sm rounded-btn" href="asd">Contact</a>
      </div>
    </div> 
    <div class="navbar-end">
      <button class="btn btn-square btn-ghost">
        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="inline-block w-6 h-6 stroke-current">     
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"></path>                     
        </svg>
      </button> 
      <button class="btn btn-square btn-ghost">
        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="inline-block w-6 h-6 stroke-current">             
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>             
        </svg>
      </button>
    </div>
  </div>
</div>

<style>
  /* If your pages have any 'idicator' items (See -> https://daisyui.com/components/indicator), 
     use 'z-index: 2;', if not, feel free to use 'z-index: 1;'
     
     Whatever the case, make sure your 'z-indexes' in the 'styles.css' I've put are this one + 1 higher.

     E.G. If this is '2', the one in the 'styles.css' must be '3'.
     If this is '1', the one in the 'styles.css' must be '2'.
     If this is '34', the one in the 'styles.css' must be '35'.

     It's just in case, so we can avoid possible overlappings when scrolling.
  */
  .navigation-bar-container {
    z-index: 2;
  }
</style>

CustomDrawer.svelte:

<script>
  import { toggleCustomDrawer } from '../stores/stores.js';

  let customDrawerOn;

  // Subscribe to the 'writable()' to listen for changes / be able to change the value.
  toggleCustomDrawer.subscribe(value => {
    customDrawerOn = value;
  });

  // Change the value of the 'writable()'.
  function toggleCustomDrawerOff() {
    toggleCustomDrawer.update(value => false);
    console.log("[CustomDrawer.svelte] toggleDrawerOff(): <<< " + customDrawerOn + " >>>");
  }

</script>

<!-- The outermost '<div>' container is the drawer container.
     Its first child functions as the "overlay".
     Its second child functions as the "menu".

     Class directive:

     Using a ternary operator we set the classes based on the value of our local variable, but
     because this component has used the '.subscribe()' method on the store 'writable()' and subsequently
     assigned its value to our local variable, whenever said 'writable()' changes, so will our local one.
     
     When it does, the class styles will be updated/changed automatically and so will the DOM elements we see
     in the Browser.
-->
<div class={customDrawerOn ? 'custom-drawer-container drawer-open' : 'custom-drawer-container drawer-close'}>
  <div class="custom-drawer-container-overlay" on:click={toggleCustomDrawerOff}></div>
  <ul class="p-4 w-80 menu bg-base-100">
    <li class="text-red-700" on:click={toggleCustomDrawerOff}>
      <!-- The comment below makes VSCode ignore the missing "href" warning. -->
      <!-- svelte-ignore a11y-missing-attribute -->
      <a class="btn btn-ghost rounded-btn text-red-500">Close Menu</a>
    </li>
    <li>
      <a class="btn btn-ghost rounded-btn" href="asd">Home</a> 
    </li>
    <li>
      <a class="btn btn-ghost rounded-btn" href="asd">Portfolio</a> 
    </li>
    <li>
      <a class="btn btn-ghost rounded-btn" href="asd">About</a> 
    </li>
    <li>
      <a class="btn btn-ghost rounded-btn" href="asd">Contact</a>
    </li>
  </ul>
</div>

<style>
</style>

styles.css:

(Copy/Paste this in an editor that reads .css files for proper highlighting and easier reading.)

  @tailwind base;
  @tailwind components;
  @tailwind utilities;

  /*  ***NOTE: PostCSS formatting except for 'z-index: 3;' which was not available 
    as an option with the initial TailWindCSS configuration. 
   
  For the definitions of each, see the following sections at https://tailwindcss.com/docs/installation

    - Position 
    - Top / Right / Bottom / Left
    - Visibility
    - Flex
    - Grid
    - Max Height
    - Transitions
    - Opacity

  For more info on 'bg-neutral-focus', see the "Colors" section of DaisyUI at https://daisyui.com/core/colors */

/* Position of the drawer container. */
.custom-drawer-container {
  @apply fixed;
  @apply inset-0;
  @apply flex;
  
  @apply grid;
  @apply max-h-screen;
  @apply col-start-1;
  @apply row-start-1;
  z-index: 3;           /* 'z-index' of '.navigation-bar-container' + 1 (See -> [CustomHeader.svelte] for more info) */
}

/* All of the children of the container will have these settings regardless of whether the
   '.custom-drawer-container' is in "opened state" or in "closed state". */
.custom-drawer-container > * {
  @apply transition-all;
  @apply duration-300;
  z-index: 3;           /* 'z-index' of '.navigation-bar-container' + 1 (See -> [CustomHeader.svelte] for more info) */
}

/* Drawer overlay */
.custom-drawer-container-overlay {
  @apply opacity-0;
  @apply invisible;
  @apply col-start-1;
  @apply row-start-1;
  @apply bg-neutral-focus;
}

/* Change the index of the container while it's in "opened state" to make sure it's above the rest of the elements. */
.custom-drawer-container.drawer-open {
  z-index: 3;                 /* 'z-index' of '.navigation-bar-container' + 1 (See -> [CustomHeader.svelte] for more info) */
}

/* Makes the '.custom-drawer-container' click-through when in "closed state". 
   
   See -> https://stackoverflow.com/questions/3680429/click-through-div-to-underlying-elements for more info.

   For an explanation as to why it was used, see my post. */
.custom-drawer-container.drawer-close {
  @apply pointer-events-none;
}

/* Original position of the overlay and <ul></ul>.

See -> https://developer.mozilla.org/en-US/docs/Web/CSS/Adjacent_sibling_combinator for more info. */
.custom-drawer-container > .custom-drawer-container-overlay + * {
  @apply col-start-1;
  @apply row-start-1;
  @apply -translate-x-full;
}

/* The overlay's position for the transition when the drawer container is in "opened state". 
  When in "closed state", they'll revert back to the original position. */
.custom-drawer-container.drawer-open > .custom-drawer-container-overlay {
  @apply visible;
  @apply opacity-40;
  @apply cursor-pointer;
}

/* Position for any children that come IMMEDIATELY after the overlay element (i.e. '<ul></ul>') when the drawer
  drawer container is in "opened state". When in "closed state", they'll revert back to the original position.*/
.custom-drawer-container.drawer-open > .custom-drawer-container-overlay + * {
  @apply translate-x-0;
}

__layout.svelte:

<script>
  import '../styles.css';
  import CustomHeader from "$lib/CustomHeader.svelte";
  import CustomDrawer from "$lib/CustomDrawer.svelte";
</script>

<CustomHeader />

<CustomDrawer />

<!-- The 'index.svelte' will go here. -->
<slot></slot>

<!-- You could put a footer here. -->

<style>
</style>

Why did I use pointer-events?

Without it, I always kept running in at least one of the problems described above (regardless of whether I used fixed positioning or not) - with this, not only do I get to use fixed positioning, which in turn makes the use of z-indexes simpler, but it won't cause any issues in the future because it's applied to an element that is only supposed to act as a "container" (no actual user interaction like clicking/hovering/dragging/etc. - and if you want to, for example, drag-n-drop an image to your page, you'll still be able to do that because the drag-n-drop will "pass through" the container and into your element where you handle that event).

Doombringer
  • 596
  • 4
  • 19