Search code examples
csstypescriptfrontendsveltesvelte-transition

How to toggle CSS class instantly when Svelte transition begins?


EDIT: I'm on Svelte 3.55.


I am attempting to code a Sidebar in Svelte and have run into issue related to how Svelte transitions work with dynamic classes.

Sidebar is supposed to fly into view when a button is clicked. I am using the transition:fly for it. There is also a Dimmer component covering the entire screen, which fades into view during Sidebar's fly animation. To close the Sidebar user must click the Dimmer. Sidebar then flies out of view, and Dimmer fades out. And here is my problem.

Where the Dimmer is in its fade out animation, the page underneath cannot be interacted with. Clicking anywhere registers on the dimmer itself, Sidebar stops its fly out animation and jumps back into view.

I tried to solve it by appending isFading class to the Dimmer when the outro animation starts - the class would add pointer-events: none to the Dimmer when it fades out. But the class is appended with a delay and this solution simply does not work.

My code looks as follows:

<script lang="ts">
  import { fade } from 'svelte/transition'
  import { isSidebarOpen, toggleSidebar } from '../state'

  let isOpen: boolean = false
  let isFading: boolean = false

  isSidebarOpen.subscribe((value) => (isOpen = value))
</script>

<div class="sidebar">
  {#if isOpen}
    <div
      class="sidebar__dimmer"
      class:isFading
      on:click={toggleSidebar}
      on:outrostart={() => (isFading = true)}
      on:introstart={() => (isFading = false)}
      transition:fade={{ duration: 500 }}
    />
  {/if}
  <div>Lorem opsum dolor sit amet</div>
</div>

<style lang="scss" scoped>
  .sidebar {
    z-index: 9999;
    position: fixed;
    inset: 0;
    display: flex;
    overflow: hidden;
    pointer-events: none;

    &__dimmer {
      position: absolute;
      inset: 0;
      pointer-events: all;
    }

    &__dimmer.isFading {
      pointer-events: none;
    }
  }
</style>

My question is: how do I append the isFading class to the component instantly when the outro animation starts?


Solution

  • I managed to overcome this issue by applying fading class to the sidebar component instead of dimmer one. I didn't need an additional variable to check if the component is transitioning - I reused the isOpen instead.

    <div class="sidebar" class:fading={!isOpen}>
      {#if isOpen}
        <div
          class="sidebar__dimmer"
          on:click={toggleSidebar}
          transition:fade={{ duration: 500 }}
        />
      {/if}
      <div>Lorem opsum dolor sit amet</div>
    </div>
    
    <style lang="scss" scoped>
      // ...
      .sidebar.fading .sidebar__dimmer {
        pointer-events: none;
      }
    </style>
    

    H.B. answer also gave me an idea to add an inert attribute to sidebar whenever isOpen is false, following how the Svelte 4+ handles transitions:

    <div class="sidebar" inert={!isOpen}>
      // ...
    </div>