Giter Club home page Giter Club logo

Comments (10)

trusktr avatar trusktr commented on June 3, 2024 1

@que-etc If you do it that way I just mentioned, it will solve the jumpyness that @EcutDavid mentioned because all sizing will be synced to animation frames.

from resize-observer-polyfill.

trusktr avatar trusktr commented on June 3, 2024

I just read over the spec some more. Basically, to make the polyfill be as close to the spec as possible, resize callbacks should be fired at the end of each animation frame, but see WICG/resize-observer#37.

Currently the polyfill doesn't fire on each frame, and can fire in between user-requested animation frames because the setTimeout in throttle will fire at a random time in the future which can be anytime between other user-enqueued macrotasks that also call requestAnimationFrame.

To make it possible to run the resize observer callbacks at the end of each frame, throttle would need to be 0 as above, and then the polyfill can monkey patch requestAnimationFrame so that it runs the size-checking code at the end of the next requested frame.

For example the monkey patching would be like the following (untested):

const realRAF = window.requestAnimationFrame
let realFrame = null
const nextCallbacks = []

window.requestAnimationFrame = function(callback) {
    if (typeof cb != 'function')
        throw new TypeError("Failed to execute 'requestAnimationFrame' on 'Window': The callback provided as parameter 1 is not a function.")

    nextCallbacks.push(callback)

    if (!realFrame) realFrame = realRAF(function(now) {
        const currentCallbacks = [...nextCallbacks]

        realFrame = null
        nextCallbacks.length = 0

        for (const cb of currentCallbacks)
            cb(now)

        // run resize observer checks and callbacks here.
    })
}

// Also make sure window.cancelAnimationFrame works...

Then this will ensure that resize observer callbacks always happen between animation frames and browser paint like in the spec.

from resize-observer-polyfill.

trusktr avatar trusktr commented on June 3, 2024

Here's a better one that doesn't create a new array every frame so less GC (untested):

const realRAF = window.requestAnimationFrame
let realFrame = null
const callbacks = []

window.requestAnimationFrame = function(callback) {
    if (typeof cb != 'function')
        throw new TypeError("Failed to execute 'requestAnimationFrame' on 'Window': The callback provided as parameter 1 is not a function.")

    callbacks.push(callback)

    if (!realFrame) realFrame = realRAF(function(now) {
        let callbackLength = callbacks.length
        realFrame = null

        while (callbackLength--)
            callbacks.shift()(now)

        // run resize observer checks and callbacks here.
    })
}

// Also make sure window.cancelAnimationFrame works...

from resize-observer-polyfill.

que-etc avatar que-etc commented on June 3, 2024

Oh, I definitely need to take my time to think about it. Thanks!

from resize-observer-polyfill.

que-etc avatar que-etc commented on June 3, 2024

Well, I have to admit that I don't really get it.

For all I know, the native ResizeObserver implementation fires notifications between Layout and Paint events. So, what do you mean by syncing up with the requestAnimationFrame and how does tampering with the host function help to invoke callbacks between those two events?

(1) rAF -> (2) Layout -> (3) Paint

I mean, how do we invoke callbacks right after the layout has been recalculated? In your example it still happens at the stage 1, because executing our code as the last element in the queue doesn't mean that we are putting it between stages 2 and 3.

from resize-observer-polyfill.

EcutDavid avatar EcutDavid commented on June 3, 2024

Hi @que-etc, @trusktr may or may not related to the "20ms"
I made a codepen for replicate a issue in my recent project.

It's simply make the square fit to document body while resizing.
image

In Safari, it's noticeable that the square "jumps", if I replace the observer with window.onresize, it will be better.

I tried change the 20 to 1 and 0, it's not improved much.

What do you think? Thanks

from resize-observer-polyfill.

que-etc avatar que-etc commented on June 3, 2024

@EcutDavid

Could you please give a link to codepen?

from resize-observer-polyfill.

EcutDavid avatar EcutDavid commented on June 3, 2024

@que-etc sorry, my mistake!
https://codepen.io/davidguan/pen/qVoKoa

Thanks!

from resize-observer-polyfill.

trusktr avatar trusktr commented on June 3, 2024

@que-etc

I mean, how do we invoke callbacks right after the layout has been recalculated?

I didn't think layout was asynchronous, but that Layout happens synchronously. (Am I mistaken about this?) If I'm correct, this would be why people say to "batch" read operations and to batch write operations, because read operations rely on the layout calculations from synchronous write operations. For example.

I just realized, the user's animation frames may also have microtask deferrals (f.e. Promise resolutions that fire in the microtask immediately following the user's animation frame). So, I think the polyfill should also ensure that resize observation handlers are fired after those microtasks as well. I'd modify my above (untested) example to this:

const realRAF = window.requestAnimationFrame
let realFrame = null
const callbacks = []

window.requestAnimationFrame = function(callback) {
    if (typeof cb != 'function')
        throw new TypeError("Failed to execute 'requestAnimationFrame' on 'Window': The callback provided as parameter 1 is not a function.")

    callbacks.push(callback)

    if (!realFrame) realFrame = realRAF(function(now) {
        let callbackLength = callbacks.length
        realFrame = null

        while (callbackLength--)
            callbacks.shift()(now)

        Promise.resolve().then({
          // run resize observer checks and callbacks here, in a future microtask.
        })
    })
}

// Also make sure window.cancelAnimationFrame works...

What I added is the Promise.resolve().then(). Microtasks are fired in the order they are registered in, so the user's microtasks will fire before our microtask, ensuring that our calculations truly happen after the user's animation frame.

from resize-observer-polyfill.

trusktr avatar trusktr commented on June 3, 2024

@que-etc Alright, I just re-read this, this, and this.

Basically:

  • Mutation Observer callbacks happen as microtasks and are queued immediately during synchronous modification of the DOM (search for instances of "queue a mutation record" in the same document).
  • Microtasks for the current task are fired before DOM rendering (see step 6 and 7 of the processing model)
  • After microtasks are fired (all MutationObserver callbacks have been fired) then step 12 in that Processing Model is updated to run resize observer callbacks synchronously. Note that Layout happens there synchronously in the while-loop.

The only way to emulate this, that I know of, is using the Promise.resolve().then() trick that I showed in my last example. What will happen is that all the user's microtasks queued from the animation frame will all be fired, and the last microtask to fire will be the one that triggers all the resize calculations.

So what you have to do is (you may already be doing one or more of these steps):

  • Listen to DOM changes using MutationObserver like you already do.
  • In MutationObserver callbacks, push the associated resize callback into a list.
  • Use requestAnimationFrame (the hacked version above) to request an empty animation frame (this ensures the Promise.resolve().then() will be fired even if the user has not manually requested any frames.
  • The hacked requestAnimationFrame will call all the resize callbacks of your list after all user frames (and all of their microtasks) have been fired because your microtask will always be the last microtask to fire.

This will ensure almost identical behavior as the actual ResizeObserver spec says. It's not physically possible (as far as I know) to hook into the HTML processing model, we only have the ability to push microtasks into the queue and to monkey patch existing globals.

Optionally, you can make an optimization that prevents an empty requestAnimationFrame call if existing frames are detected to be queued.

from resize-observer-polyfill.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.