javascripttypescriptsvelteserver-side-renderingsveltekit

How to check when window becomes available?


I am new to SvelteKit and I am not familiar with SSR. I have recently learned that window is only defined after the client-side has been loaded, which can be determined with the onMount hook.

But here's something that I am not clear on. Let's say I have the following component:

<script lang="ts">
  const getRem = (rem: number) => rem * 
    parseFloat(getComputedStyle(document.documentElement).fontSize)
  const sizeOfSomeOtherElement = getRem(4) // returns the px value of 4rem
  const sizeOfThisElement = sizeOfSomeOtherElement * 0.5
</script> 

<div style="width: {sizeOfThisElement}rem"></div>

Now I understand in this particular example I can just use CSS calc, but let's pretend that I have to use a JavaScript value for the sake of argument. Here, I want to dynamically get the value of a rem as it could be different based on the user.

This will throw an error telling me that getComputedStyle is not defined, as window is not defined. Now, I could delay the rendering of the element and wait until the component is mounted once like so:

<script lang="ts">
let isLoaded = $state(false)
onMount(() => { isLoaded = true })
/* ... other conditionals ... */
</script>

{#if isLoaded)
  <div style="width: {sizeOfThisElement}rem"></div>
{/if}

But this delays the rendering of the component by a slight, but noticeable amount. It doesn't seem to be the right solution to me, as I don't need the component to be mounted, I just need the variable window to be available, which I would think comes before mounting.

What's the best way to tackle this? Am I misunderstanding anything?


Solution

  • There will always be a delay between the server-side provided HTML being rendered and the component script being executed and updating it.

    I tried other methods, that are focused on a single element (like bind:this and use:action), but they have the same issue.

    You would essentially have to run code synchronously while the element is being rendered, to prevent this entirely. It is technically possible to add a plain <script> by wrapping it in an element but then you are limited to native JS and also cannot reference anything in the component script.

    E.g.

    <div>
      DIV to adjust
    
      <script>
        const getRem = rem => rem * 
          parseFloat(getComputedStyle(document.documentElement).fontSize);
    
        const target = document.currentScript.parentElement;
        const sizeOfSomeOtherElement = getRem(4);
        target.style.width = (sizeOfSomeOtherElement * 0.5) + 'rem';
      </script>
    </div>
    

    Approaches like this are sometimes used for dealing with theme switches that are stored in localStorage to prevent flashes.