Compiling scss on client side to save on network
Asked Answered
C

3

5

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.

Colored answered 26/4, 2020 at 16:26 Comment(2)
only way is, you can use javascript to generate that kind of large random stars creation.Tray
@RaviNila That's right. My question is more about this kind of issue in general, I'll update itColored
R
3

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().

Rauwolfia answered 9/5, 2020 at 19:30 Comment(1)
Actually, that's exactly the solution I went with in this specific case. Thank you for sharing!Colored
S
6

The Sass compiler is written in C++, so it would be more or less impossible to run it inside a browser.

Instead you could use Less, that runs pretty well inside a browser.

index.html

<link rel="stylesheet/less" type="text/css" href="styles.less" />
<script src="//cdnjs.cloudflare.com/ajax/libs/less.js/3.9.0/less.min.js" ></script>

multiple-box-shadow.js

(this plugin is needed, because Less cannot generates random numbers by itself)

registerPlugin({
  install: function(less, pluginManager, functions) {
      functions.add('multiple-box-shadow', function(n) {
          return Array(n.value).fill().map(function() {
              var x = Math.floor(Math.random()*2000);
              var y = Math.floor(Math.random()*2000);
              return x+'px '+y+'px #FFF';
          }).join(',');
      });
  }
});

styles.less

@plugin "multiple-box-shadow";

@shadows-small:  multiple-box-shadow(700);
@shadows-medium: multiple-box-shadow(200);
@shadows-big:    multiple-box-shadow(100);

#stars {
    width: 1px;
    height: 1px;
    background: transparent;
    box-shadow: @shadows-big;
    animation: animStar 50s linear infinite;
}

For your specific example:

  • styles.css: 41'277 B (5'936 B compressed)
  • styels.less: 1'856 B (586 B compressed)

In the console, less outputs the generation time, on my computer it takes between 100ms and 200ms.

I think the benefit of compiling on client side is very low. It's very uncommon to have a compiled CSS that is bigger than its source, mainly because CSS compilers minifies their output.

Swordtail answered 7/5, 2020 at 18:2 Comment(2)
Thank you for the answer! "It's very uncommon to have a compiled CSS that is bigger than its source". What do you mean by that? The source Less is smaller than the output css? That's correct, of course, but how does that make "the benefit of compiling on client side very low"? Seems like the opposite is derived from thatColored
What I meant was that in most of the cases, generated CSS are small, and downloading the compiled file would be quicker than generating it on client side (for you case: download 0.5 ms + generation 150ms VS 5 ms download). But indeed, for some very specific usecases, it can save a bit of bandwidth.Swordtail
H
4

Wouldn't the savings on network bandwidth outweigh the (minuscule) cost of generating the css?

A possible argument against doing that could be that yes, you might save on bandwidth downloading a smaller SCSS file compared to CSS, but also have to supply the client with the compiler code, which (assumption) outweighs the difference in file size compared to css.

In lack of a better example I take Yann's numbers and LESS under the assumption that an imaginary SCSS compiler would show analogue behaviour:

For your specific example:
- styles.css: 41'277 B (5'936 B compressed)
- styels.less: 1'856 B (586 B compressed)

  • less.min.js: 167'812 B

Unless browsers would come pre-equipped with a SCSS-Compiler therefore questioning the need for CSS file type in the first place.

Highjack answered 8/5, 2020 at 14:37 Comment(0)
R
3

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().

Rauwolfia answered 9/5, 2020 at 19:30 Comment(1)
Actually, that's exactly the solution I went with in this specific case. Thank you for sharing!Colored

© 2022 - 2024 — McMap. All rights reserved.