Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance: EQCSS timers, throttle #85

Open
tobireif opened this issue Jan 11, 2018 · 8 comments
Open

Performance: EQCSS timers, throttle #85

tobireif opened this issue Jan 11, 2018 · 8 comments

Comments

@tobireif
Copy link

tobireif commented Jan 11, 2018

Hi Tom!

Please load the Grid demo
https://tobireif.com/demos/grid/
and set the window width to eg ~900px.

Click on "Layout 2", note the delay in the layout update.
Click on "Layout 1", note the delay in the layout update.

I did a perf profile, the perf hits involve EQCSS timers and EQCSS throttling.

I already tried to circumvent the throttling.

button.addEventListener("click", function(){
  EQCSS.apply();
});
[
  "DOMContentLoaded", "load", "resize", "orientationchange"
].forEach(function(e) {
  window.addEventListener(e, function(){
    EQCSS.apply();
  });
});

I hope you can offer an EQCSS option for disabling all delays, timers, throttling, etc.

Any tips in the meantime?

Thanks for EQCSS!

@tobireif
Copy link
Author

P.S.

Ideally there'd be a high performance version of the lib without any timers, throttling, or any other delays. (And perhaps it would also make the lib faster to reduce/avoid DOM writes such as eg these: from one element:
data-eqcss-1-0-prev="" data-eqcss-2-0-prev="" data-eqcss-3-0-prev="" )

Just ideas 😀

In any case I hope that the perf issue I described in the first message can be resolved somehow.

@tomhodgins
Copy link
Contributor

Nice demos! I see the difference you're talking about between the two, and I can understand wanting to circumvent the throttle. I'm not sure we'll take it out of the main EQCSS library but feel free to make an alternate build that lacks it.

Originally EQCSS didn't have a throttle, but then at some point Firefox's performance degraded to a point where adding a throttle was a necessity.

2016 was a long time ago you might say, maybe with the new Firefox Quantum we can finally get the performance boost we need to remove the throttle? Not quite, it appears the new version of Firefox is worse for event-driven styling:

They consider fixing one of these problems a low priority:

Emilio says this bug is not a high priority because a web developer wouldn't design a real website like this.

Lol! So I guess I'm either a) not a real web developer, or b) my sites aren't real websites haha. Hopefully they find a way to fix their browser.

So the throttle should stay in the main EQCSS.js library unfortunately, and as buttery smooth and nice as it is calling EQCSS.apply() directly in all other browsers, try testing it in Firefox to make sure you're not locking it up entirely.

Now, separately for the idea of a 'high-performance EQCSS' I have a few ideas:

  • a stripped-down version of EQCSS.js
  • a simpler rewrite of the EQCSS functionality

I've done both of these, so I'll let you measure the results or see what you might like to use. For one of our users who was only using the width feature, $this, and $parent, we were able to simply remove the code for the other features, and for the other selectors, and the library ran faster for the features it had left: https://gist.github.com/tomhodgins/e3a8196acc41e52849a93916c9d06e6c

The second alternative is something I started last month, I've been experimenting with 'event-driven virtual stylesheets' outside of EQCSS using a pattern I call 'JS-in-CSS', and so I rewrote the features and functionality of EQCSS as a JS-in-CSS plugin that's much smaller than EQCSS.js. It doesn't parse a custom CSS-like syntax, but you're able to use JavaScript to call it and the inner stylesheet format is the same as EQCSS: https://gist.github.com/tomhodgins/fa127a2d98ff8d4dcb936bdd007da0b0

One downside is that JS is only aware of pixels for breakpoints, where with EQCSS we tried to parse any CSS units for the breakpoints, but perhaps that's where performance can be improved. The following two examples are equivalent, but I bet the EQCSS JS-in-CSS version beats the EQCSS.js regular version for performance:

EQCSS Demo

<input>

<style>
  @element input and (min-characters: 5) {
    :self {
      background: lime;
    }
  }
</style>

<script src=http://elementqueries.com/EQCSS.js></script>

EQCSS-in-JS helper function

<input>

<style>
  ${eqcss('input', {minCharacters: 5}, `
    :self {
      background: lime;
    }
  `)}
</style>

<script src=https://rawgit.com/tomhodgins/fa127a2d98ff8d4dcb936bdd007da0b0/raw/34976ac3615407294b07879741721a6a254d6569/eqcss-mixin.js></script>
<script src=https://staticasset.s3.amazonaws.com/jic.js></script>

Even though they function nearly equivalently, and even though the second example looks a little strange, every character of the second demo is either standard CSS or standard JS too, so that's an added advantage. You get the same result as if you populated a <style> tag or stylesheet in the CSSOM with the result of running this in JS:

eqcss('input', {minCharacters: 5}, ':self { background: lime; }')

Hopefully that helps point you in the right direction. Having EQCSS.js working in IE8 and up makes me not want to change it too drastically, but gives me the freedom to experiment a little with other ideas that may not have as broad support needs :D

@tobireif
Copy link
Author

Thanks for your reply!

(Also see my comments on the bug pages you linked.)

Perhaps you could offer an option for disabling all throttling?
eg

EQCSS.disableThrottling = true;

Throttling would stay in the lib, and it would still be the default, but lib users (including me) could disable it by adding just one line.

@tobireif
Copy link
Author

Adding this line resolved all my woes:

EQCSS.throttle = EQCSS.apply;

Now I have this in total:

/*
Necessary for fixing this issue:
With the window at ~600px wide, click "Layout 2", then click
"Layout 1" - the layout update happens after a lag.
*/
// 🐒-patching for immediate layout updates:
EQCSS.throttle = EQCSS.apply;

/*
Necessary for fixing this issue:
With a viewport of eg 600px, cLick on button "Layout 2", then on
"Layout 1". Now click anywhere, eg on "Fast" - the heading size jumps.
*/
function eqcss() {
  setTimeout(function(){
    EQCSS.apply();
    EQCSS.apply();
  }, 0);
}

buttons.forEach(function(button, index) {
  button.addEventListener("click", function(){
    eqcss();
  });
});

// and I kept this, just to be sure 😁
[
  "DOMContentLoaded", "load", "resize", "orientationchange"
].forEach(function(e) {
  window.addEventListener(e, function(){
    eqcss();
  });
});

At https://tobireif.com/demos/grid/ all's good now 😁
(including in Firefox)

Feel free to use any of this for the lib & docs.

@tomhodgins
Copy link
Contributor

Hi Tobi!
When I see this:

Perhaps you could offer an option for disabling all throttling?

To me it reads as:

Perhaps you could offer an option for breaking all Firefox?

If the throttle was necessary because EQCSS-powered sites in the wild became broken as Firefox updated, making it easy for people to build in ways that will be broken in Firefox seems irresponsible :/ I'm not sure Firefox performance is improving in this area, so the throttle may become more necessary as time goes on, rather than less necessary. Originally it wasn't required at all.

I'm glad you've found a workaround—that's similar to what I'd do to workaround the throttle as well, but I don't think bypassing the throttle would be a good option to offer to people who don't know how to bypass it themselves, because they are likely to enable it without testing their use-cases thoroughly in firefox to make sure it's okay—if it appears faster and smoother in every browser (apart from Firefox) without a throttle, I imagine most people who notice that would disable it given the choice.

I do think the EQCSS-in-JS function might be usable in Firefox without a throttle as-is simply because it's smaller and can run faster. Compare these two demos, one is EQCSS and one is EQCSS-in-JS. If you're looking for better performance than EQCSS.js I'd recommend moving that direction, rather than trying to optimize EQCSS.js:

I think this would be a better way to gain performance and avoid a throttle at the same time :D

@tobireif
Copy link
Author

[words I never said:] Perhaps you could offer an option for breaking all Firefox?

Again:
At https://tobireif.com/demos/grid/ all's good now
(including in Firefox)

You could add the option with a specific warning (eg does it only affect scroll-performance?), or don't add it - it's up to you.

I think [EQCSS-in-JS] would be a better way to gain performance and avoid a throttle at the same time

I enjoy writing the element queries in one place with the other CSS, and the code I added is an effective way to avoid throttling and to get great perf 😀

@mmzoo
Copy link

mmzoo commented Sep 23, 2019

Hi,

I'm trying to wrap my head around different element query solutions and their performance.

I stumbled upon css-element-queries and their approach appears to avoid event listeners on "resize" as you do.

Am I missing something, or is their approach much simpler than the one this library here offers?

I know it's not specifically related to the throttling issue you're discussing here, but I'm trying to understand why throttling is needed in the first place, when the desired goal - as far as I can tell - could be reached without it.

Thank you.

EDIT: OK, after heaving read this article I start to believe that ResizeObserver is the future-proof way to go.

@tomhodgins
Copy link
Contributor

@mmzoo EQCSS was designed to be dropped into any page and hopefully Just Work™, and a throttle wasn't required originally, but at one point in time Firefox changed something about scroll events and unless EQCSS throttled the number of computations, existing websites were totally frozen and couldn't even be scrolled. So it's a historical workaround for a bad browser at one point in time.

You're right that ResizeObserver shows a lot of promise for providing the knowledge for when an element query should reprocess if it's a width or height-based condition like min-width, and Mutation Observers can be used for conditions like min-children and max-characters too.

These days I find myself applying element queries with much smaller abstractions, like jsincss-element-query which isn't hooked up to any Observers or listeners - so you're free to integrate it into your code and hook up the listeners that should drive its recalculation! The most recent thing I've been doing is parsing valid custom CSS like this into the JS runtime it needs to run what it found automatically - so you write CSS and deliver self-contained JS that runs what you wrote.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants