csstypescriptsveltestyling

Svelte/TS applying styles through a foreach loop doesn't modify the styling


I've been trying to create a cool "bubble" effect on a website of my, but I cannot get the styling to change in a foreach loop.

There are no errors in the console, so I'm not sure what I should be doing to debug this.
Here is the code:

<script lang="ts">
    let bubbles:HTMLDivElement[]=[];
    let loaded:number=0;
    const ballAmount:number=15;
    function moveBubbles():void {
        bubbles.forEach((element)=>{
            element.style.top=Math.round((Math.random())*100)+"vh;";
            element.style.left=Math.round((Math.random())*100)+"vw;";
        });
        setTimeout(moveBubbles,15000);
    }
    moveBubbles();
</script>

<div class="bubbles">
    <div class="circles">
        {#each {length: ballAmount} as _, i}
            <div bind:this={bubbles[i]}
                class="bubble"
                style="
                    width: {Math.random()*25}vw;
                    opacity: {Math.random()*0.1};
                    top:{Math.random()*90}vh;
                    left:{Math.random()*100}vw;
                "></div>
        {/each}
    </div>
    <div class="main">
        <slot></slot>
    </div>
</div>

<style>
    .bubble {
        transition: top left 15s linear;
        aspect-ratio: 1;
        position: absolute;
        background-color: var(--primary);
        border-radius: 100%;
        opacity: 0.02;
        z-index: 0;
    }
    .bubbles {
        width: 100vw;
        height: 100vh;
    }
    .main * {
        z-index: 5;
    }
</style>

I've tried to use on:load, but I couldn't get it to run the function, same with use:moveBubbles.


Solution

  • The assigned values are just invalid and thus ignored due to a rogue ; after the unit:

    Math.round((Math.random())*100)+"vh;";
    

    Note that you are modifying the style which also has interpolated values in the template. If these values were dynamic, the changes from the script may be overridden.

    Generally, direct DOM manipulation is not recommended. Instead I would store an array of { x, y } objects and interpolate those in the template. In the script you then just change the data.

    <script lang="ts">
      const ballAmount = 15;
      let bubbles = randomLocations();
      setInterval(() => bubbles = randomLocations(), 1000);
    
      function randomLocations() {
        return Array.from({ length: ballAmount }, () => ({
          x: Math.round(Math.random() * 100) + "vw",
          y: Math.round(Math.random() * 100) + "vh",
        }));
      }
    </script>
    
    <div class="circles">
      {#each bubbles as { x, y }}
        <div class="bubble"
            style="
              width: {Math.random() * 25}vw;
              opacity: {Math.random() * 0.1};
              top: {y};
              left: {x};
            "></div>
      {/each}
    </div>
    

    (Timeouts and intervals should usually be cleaned up when the component is unmounted/destroyed to avoid leaks. Skipped that in the code sample for brevity.)

    REPL