Hacker News new | past | comments | ask | show | jobs | submit login
Show HN: SentinelJS – Detect new DOM nodes using CSS selectors (github.com/muicss)
90 points by andres on Sept 18, 2017 | hide | past | favorite | 41 comments



Nice work! I've also done a similar thing (https://github.com/rstacruz/onmount), and it's really useful in managing behaviors in a page.

For those wondering what this is for, I've written a document with guidelines in building scalable non-SPA apps leveraging this: http://ricostacruz.com/rsjs


Wow, rsjs is really neat! Thanks! Totally going to use that on our dashboard.


Is it just me or do these approaches seem hacky? If you were using something like Angular or React wouldn't you plug into lifcycle events that are exposed by the library like Abgular's OnInit?


if you were using something like Angular or React to manage your full page, then by all means run with it! RSJS is an alternative to that that's more suited for "traditional" sites rendered on the server (eg, traditional Rails and Rails-like setups).


Rsjs looks pretty great, thanks.


Don't we have mutation observers for this exact purpose? or is there a benefit to this approach?


I have used mutation observers to do exactly this. They are very simple to use.


Can you give an example of how you would do this with mutation observers?



How does it accomplish it in 680 bytes? I tried reading the code but don't understand it.. there is a lot of stuff about css and keyframes that i don't get.


It adds a stylesheet to accept new stylerules. For every element set (CSS selector) you want to watch it adds a new stylerule that makes a no-op animation happen (transform:none -> transform:none). Every time such a node that matches your selector is added to the document, this animation is triggered. When this happens, Sentinel publishes an event to whatever handlers you ask it to, by listening to the 'AnimationStart' event emitted when an animation happens. Pretty clever, but simple, and I think this is not the first source to suggest this approach for watching DOM modification. Note, the unminified dist/sentinel.js is not 682 bytes!


it will break when browser optimize a null animation, just like tons of code used to detect if some ad was in view in the past broke.

edit: already happened once with this very project. they started animating clip, but then ie optimised it and now they use outlineColor.


Wow that is just super clever and I'm guessing super fast also since it is triggered by the browser. I was thinking more on the lines of comparing nodes using setInterval which would of course never work because it would be slow and CPU intensive!


Looks like it adds a keyframe animation to the elements, and when a child node is inserted that animation is triggered, and emits an event.

Am on my phone tho, so may have misread.


From the code, it adds `animationstart` event that will be fired for any newly injected DOM https://github.com/muicss/sentineljs/blob/master/src/sentine...

It adds a faux `animation` keyframe for the specified CSS selector.


Detailed write up of how it is implemented, and why mutation events are not suitable:

http://www.backalleycoder.com/2012/04/25/i-want-a-damnodeins...


But aren't mutation events deprecated since forever? Is the newer replacement mutation observer also unsuitable?


MutationObserver could handle this case instead of the animationstart hack yeah. Here's one impl I found: https://github.com/azu/wait-for-element.js/blob/master/lib/w... (also rstacruz's referenced onmount lib also uses MutationObserver/matches())

(Mutation events deprecated since ~5 years ago, yes. )


I think the article might answer that, if I understand your question correctly.


The DOM element arrival detection is based on animation events in Sentinel. Sentinel adds global style rules that cause newly born elements to start animation and so generate "animationstart" event that the library catches.

That appears if not hacky then a bit heavyweight - 2 passes of layout needed at least.

But I do understand the need for this. In sciter I've added special CSS property aspect that takes name of the function to execute when element gets mounted into the DOM and before any layout on it:

    my-component { 
     aspect: MyComponent url(script/my-components.js); 
     color:blue;
    }  
and script:

    function MyComponent() {
      this // is the element just added to the DOM.
    }


Do you have an example of this technique? I just tried using your technique but I couldn't get it to work.


But how?


What's the difference between SentinelJS and DOM Mutation Observers?

I glanced at the source code, it seems this lib is using some kind of hack based on CSS animations. Why not just use Mutation Observers, which are the standard approved API to get the job done?


Mutation observers are very, very bad for performance. They're still useful for some things as they give you a lot of detail about what happened, but if you're just looking for a change on a specific set of elements this library seems like a better bet.


> Mutation observers are very, very bad for performance.

No, they are not. Mutation events are tragic for performance and are deprecated (Chrome will make that pretty obvious in the console). The Mutation Observer was designed to have reasonable performance characteristics. It's still a very active listener, but rarely is it a bottleneck.

more: https://developers.google.com/web/updates/2012/02/Detect-DOM...


I would have thought MOs were implemented in C and thus pretty fast. Either way, even if they're slow compared to this technique at present, I haven't found performance to be an issue when using them now, and no doubt they'll be optimsied further.


I might be missing a very obvious use-case here (3rd party scripts, perhaps?) but surely if a new DOM node is inserted, you most likely actioned it yourself - so why not set the innerHTML at that point?


There is some discussion about the motivation here:

http://www.backalleycoder.com/2012/04/25/i-want-a-damnodeins...


And implementation by the same author: https://github.com/csuwildcat/SelectorListener


Oh nice, thanks.

I'd actually completely forgotten about mutation observers too - they'd got mentally filed away under "browser vendors are why we can't have nice things".


You used npm to pull in nine hundred dependencies. One of them made the new node, and you want to know.

Or you're writing one of the nine hundred, and the user made the node.


For fengari-web[1] we want to watch for <script type="text/lua"> tags inserted into the document. At the moment we use a MutationObserver[2], but I should compare the speed of things. I've created a new issue to track[3]

  [1] https://github.com/fengari-lua/fengari-web
  [2] https://github.com/fengari-lua/fengari-web/blob/4279f4b3aec063062eb8baf68a065ee5b0c1d7c6/src/fengari-web.js#L132
  [3] https://github.com/fengari-lua/fengari-web/issues/15


If the author is watching this thread or anyone else. Would this work for elements not yet loaded by the browser before DOMContentLoaded?


Yes. SentinelJS will trigger a watch for elements present when DOMContentLoaded fires and for elements added dynamically afterwards. In the Quickstart example, there's an element that is part of the initial HTML payload and a button to add more dynamically: https://jsfiddle.net/muicss/rbqLbjzf/


Thanks for the response and sorry. Maybe I was not quite clear. I was attempting ask if it would trigger the event handler before DOMContentLoaded (relevant on big document where DOMContentLoaded takes 10 seconds to fire) Example on a timeline:

  0. Browser starts parsing the document
  1. Sentiel.js loads (ie. by blocking script tag)
  2. Handler is set for .my-component (ie. by inline script tag)
  3. Browser parses <div class="my-component" />
  4. Handler is fired for .my-component
  5. Browser parses rest of document
  6. DOMContentLoaded is fired.
Will the .my-component handler will always fire after DOMContentLoaded or have you experienced that it might happen before? I guess it reduces to the question if key-frame-handlers will fire before the document is fully loaded/rendered.


Yes, it comes down to when animation events fire which I believe is after DOMContentLoaded.

In MUI we detect all elements present in the DOM when DOMContentLoaded fires using document.querySelectorAll() and then use SentinelJS to detect elements added after.


I think, this is not a “shadow DOM-like,” but a “custom element-like experience.”


Fixed. Thanks.


Great work!


on(), off() seems a bit unintuitive in my opinion. I'd opt for something like .watch(), .ignore()


on() and off() are the names that every JavaScript library uses for event handlers; I don't think bikeshedding individual libraries is going to help much.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: