csssasscompilationnetwork-trafficclient-side-scripting

Compiling scss on client side to save on network


I've seen this example for an animated star background using css, and noticed that the compiled css is significantly smaller in this case as the sass generates a thousand stars in a loop.

// n is number of stars required
@function multiple-box-shadow ($n) 
  $value: '#{random(2000)}px #{random(2000)}px #FFF'
  @for $i from 2 through $n
    $value: '#{$value} , #{random(2000)}px #{random(2000)}px #FFF'

  @return unquote($value)

It made me wonder, is there a way to generate said css on the client side? Wouldn't the savings on network bandwidth outweigh the (miniscule) cost of generating the css?

I could not find an example for such a use case, does the compression of network traffic make this irrelevant?

I'm not necessarily asking about this case specifically. More of how does bandwidth vs computation-time comes into consideration(if at all). The same could be said for having js frameworks that have ways of generating HTML using more concise syntax(like ngFor in Angular) on the client side.


Solution

  • As @onewaveadrian has pointed out, it doesn't make sense to try to save some bytes by generating the CSS on the browser, but then download the SCSS or LESS compiler instead to do so.

    However, you could generate those CSS shadows using only vanilla JS on the browser, without including any additional dependency, which is going to save plenty of bytes and is probably going to run faster than a fully-fledged compiler as well.

    In order to make it even faster, the multipleBoxShadow function is using a simple while loop, string concatenation and the bitwise OR operator (|) to floor numbers way faster than Math.floor() would.

    const MAX_Y_OFFSET = 2000;
    const MAX_X = window.innerWidth;
    const MAX_Y = window.innerHeight + MAX_Y_OFFSET;
    
    function multipleBoxShadow(n) {
      let boxShadow = '';
      
      // Let's use a simple while loop and the bitwise OR operator (`|`) to round up values here
      // to run this as fast as possible:
      while (n--) {
        boxShadow += `,${ Math.random() * MAX_X | 0 }px ${ Math.random() * MAX_Y | 0 }px #FFF`;
      }
      
      return boxShadow.slice(1);
    }
    
    const { documentElement } = document;
    
    documentElement.style.setProperty('--shadows-small', multipleBoxShadow(700));
    documentElement.style.setProperty('--shadows-medium', multipleBoxShadow(200));
    documentElement.style.setProperty('--shadows-big', multipleBoxShadow(100));
    body {
      min-height: 100vh;
      background: radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%);
      overflow: hidden;
    }
    
    #stars {
      width: 1px;
      height: 1px;
      background: transparent;
      animation: animStar 50s linear infinite;
      box-shadow: var(--shadows-small);
    }
    
    #stars:after {
      content: " ";
      position: absolute;
      top: 2000px;
      width: 1px;
      height: 1px;
      background: transparent;
      box-shadow: var(--shadows-small);
    }
    
    #stars2 {
      width: 2px;
      height: 2px;
      background: transparent;
      animation: animStar 100s linear infinite;
      box-shadow: var(--shadows-medium);
    }
    
    #stars2:after {
      content: " ";
      position: absolute;
      top: 2000px;
      width: 2px;
      height: 2px;
      background: transparent;
      box-shadow: var(--shadows-medium);
    }
    
    #stars3 {
      width: 3px;
      height: 3px;
      background: transparent;
      animation: animStar 150s linear infinite;
      box-shadow: var(--shadows-big);
    }
    
    #stars3:after {
      content: " ";
      position: absolute;
      top: 2000px;
      width: 3px;
      height: 3px;
      background: transparent;
      box-shadow: var(--shadows-big);
    }
    
    @keyframes animStar {
      from {
        transform: translateY(0px);
      }
      to {
        transform: translateY(-2000px);
      }
    }
    <div id='stars'></div>
    <div id='stars2'></div>
    <div id='stars3'></div>

    As you can see, I'm using CSS custom properties to pass the generated box-shadow value to CSS, so that I can use it in pseudoelements as well. If you prefer not to use CSS variables, you could use 6 <div>s instead of 3 and use the style attribute like so:

    document.getElementById('stars').style.boxShadow = multipleBoxShadow(700);
    document.getElementById('stars2').style.boxShadow = multipleBoxShadow(700);
    document.getElementById('stars3').style.boxShadow = multipleBoxShadow(200);
    document.getElementById('stars4').style.boxShadow = multipleBoxShadow(200);
    document.getElementById('stars5').style.boxShadow = multipleBoxShadow(100);
    document.getElementById('stars6').style.boxShadow = multipleBoxShadow(100);
    

    Also, if the performance of the animation itself is not good enough, you could probably adapt this code easily to draw starts (circles or squares actually) on a <canvas> instead, and animate them using Window.requestAnimationFrame().