滚动捕获不起作用,因为 Svelte 插槽位于抽屉组件(页眉)内

Scroll capturing not working because the Svelte slot is inside the Drawer component (Header)

我正在寻找一种滚动到元素以触发事件的方法。我注意到很多示例的 scrollTo()scrollBy() 等都不起作用(除了 scrollByView()button 中通过 on:click 调用或滚动使用经典 <a href="#element"></a>).

我在我的 .svelte 文件中使用像这样简单的东西进行了测试(结果 -> 什么也没发生,也没有错误)以查看滚动是否有效:

<script>
  let y;
</script>

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

做了更多挖掘,我 运行 进入 JavaScript scrollTo method does nothing,之后我再次检查我的页面,我在 DevTools 中注意到 scrolloverflow单击某些元素时会出现框。

然后点击...当然我不能滚动。没有要滚动的页面。我滚动的只是溢出内容,而 window 内容的高度在技术上保持不变。

我找到的以下代码 here,确认 <main></main> 部分导致溢出(自然)。

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

(我在我的 Svelte 网站中使用 DaisyUI 中的 navbar menu for desktop + drawer for mobile。)

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>

如果我将 <Header></Header><slot></slot> 放在 之外 __layout.svelte 滚动会按预期工作,但是 navbardrawer 部分没有。

问题:

我如何重构我的 Svelte 组件,以便抽屉保持在内容之上(即具有更高的 z-index),因为它的高度与屏幕相同 并且 未显示时仍能滚动(例如在桌面上)?

P.S。我发现的抽屉示例似乎总是以与 DaisyUI 相同的方式工作,即将您的内容 放入 中。

解决方案逻辑

解决方案背后的想法是将 navbardrawer 分成不同的组件并使用 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 应用不同的组件样式 类 用于打开和关闭抽屉时的不同元素。

[以下解决方案也可以在 VueJS 等其他框架中实现,甚至可以使用 vanilla JavaScript。]

工具:

  • Svelte / SvelteKit(在本例中)
  • TailWindCSS
  • DaisyUI(需要 TailWindCSS)

常见问题

我尝试了一些其他可能的解决方案(其中一些甚至没有使用 overlay 元素)但我总是 运行 陷入以下问题之一,其中包括:

  • 抽屉组件在关闭时会移到导航栏后面。
  • 标记为 overlayopacitybackground-color 的元素在关闭时转换时不会触发(由于抽屉容器的方式,它只会跳到“0”被隐藏了)。
  • 抽屉和标记为 drawer container 的元素必须 两者 opacity 关闭转换时减少。 (这最终看起来不错,这是我想出的第一个解决方案,但我想更接近 DaisyUI 的原始视觉过渡。)
  • 有时过渡只会对“粘性”导航栏或普通导航栏正常工作,但绝不会同时出现。

重要提示

  • 下面的代码支持“粘性”导航栏 普通导航栏(例如 Navbar section of DaisyUI 中的导航栏)。
  • 抽屉显示时启用滚动。
  • 您也可以在执行此操作之前使用 eventDispatcher, but I would recommend checking out 来执行此操作。

代码

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 这在一个编辑器中读取 .css 文件以便正确突出显示和更容易阅读。)

  @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 ->  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>

为什么我用了pointer-events

没有它,我总是在至少一个上述问题中保持运行(不管我是否使用fixed定位) - 有了这个,我不仅得到了使用 fixed 定位,这反过来使 z-indexes 的使用更简单,但它不会在将来引起任何问题,因为它应用于仅应充当“容器”的元素(没有像 clicking/hovering/dragging/etc 这样的实际用户交互。 - 如果你想,例如,将图像拖放到你的页面,你仍然可以这样做,因为拖放会“通过”容器并进入您处理该事件的元素)。