-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
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
<:Window> component #371
Comments
I like the idea because it heavily lowers the amount of code to track data from the window/document, but it is very confusing to mix "meta" tags with UI tags. |
@PaulBGD do you mean the confusion between |
What I mean is, |
Is it any different in that regard from |
Those tags aren't really used in components, not to mention on an HTML page they're put in the header - separated from the UI. |
You can put |
I just think that there's a better way, like moving this into the JS. |
Do you have an example of what that would look like? Not trying to be awkward, just struggling to understand what you mean by that |
I think the suggested method of handling this via special components is a pretty tidy way of doing this. Yeah it's a little bit of magic, but I do think it's worth it. I'm thinking that besides letting us bind to events on I'm a little concerned about how two-way binding would work here. I'd probably like as few special cases on the |
Yeah, the event listeners is the easy win. The binding thing actually only occurred to me as I was writing this issue. It would be a special case for each different binding — for example the <:Window bind:innerWidth='width' bind:innerHeight='height'/> function SvelteComponent ( options ) {
options = options || {};
this._state = options.data || {};
+ this._state.width = window.innerWidth;
+ this._state.height = window.innerHeight;
+ window.addEventListener( 'resize', this._windowResizeHandler = function () {
+ this.set({ width: this.innerWidth, height: this.innerHeight });
+ });
// ...
}
// ...
SvelteComponent.prototype.destroy = function destroy ( detach ) {
this.fire( 'teardown' );
this._fragment.teardown( detach !== false );
this._fragment = null;
+ window.removeEventListener( 'resize', this._windowResizeHandler );
this._state = {};
this._torndown = true;
}; The advantage of doing it as a binding is that it can initialise correctly without you having to declare the values in your <:Window bind:scrollY/>
<div class='parallax far' style='transform: translate(0,{{-scrollY * 0.25}})'></div>
<div class='parallax mid' style='transform: translate(0,{{-scrollY * 0.5}})'></div>
<div class='parallax near' style='transform: translate(0,{{-scrollY * 0.75}})'></div>
<div class='content'>
<!-- scroll at normal speed -->
</div>
<style>
.parallax {
position: fixed;
}
</style>
<script>
// jk, no JavaScript needed
</script> If we went down that road, we could easily check that the user wasn't trying to use a non-implemented binding, and I don't think we'd implement stuff that relied on polling. |
So a question about 2 way binding, how would it work with SSR? Would the initial window width/height be 0? And about using scrollY, how would we handle the scroll event? Updating the entire component tree every time it's called can make scrolling really janky. |
Hmm, great question. I suppose you would kind of have to answer that question anyway if you were using that value — e.g. today you would do something like this... export default {
data () {
return {
width: typeof window !== 'undefined' ? window.innerWidth : 600,
height: typeof window !== 'undefined' ? window.innerHeight : 400
};
}
}; ...in other words you likely have to supply a fallback mechanism whatever it is you're doing. So maybe the same goes for SSR: const { code, map } = svelte.compile( input, {
generate: 'ssr',
fallback: {
window: {
innerWidth: 600,
innerHeight: 400
}
}
}); Nice thing about that is the compiler can check that fallback values have been supplied if you're relying on those values in an SSR context, whereas right now if you forgot (i.e. didn't include the
It's really no different to how you'd do it any other way — either you add this code... window.addEventListener( 'scroll', function () {
component.set({ scrollY: window.scrollY });
}); ...or Svelte does. Whether or not it affects the entire component tree is up to you — it only affects the subtree that includes the |
Hm, weren't there some read-only properties on How would work that with (i)frames? If you're about to implement that, what about adding one for |
Yeah, this is about having an easy declarative way to read those values or respond to events on
Not sure what you mean?
|
Have you on your radar to bind to the correct |
Not in a cross-window sense, no. I think that's a real edge case that probably isn't worth the extra code it would take to support |
window.addEventListener( 'scroll', function () {
component.set({ scrollY: window.scrollY });
}); Svelte updating can take a while especially if there's a lot of elements, so having it update every time you scroll (which can be called many times a second) would be a really big performance hit. I suppose in performance specific cases the developer would implement their own scroll listener though.. |
Do you have an example of Svelte being slow to update? Something we can adapt for svelte-bench? According to the benchmarks I've looked at so far it's as fast as anything out there. But yes, if you have a very complex component tree and you bind a lot of things to |
@Rich-Harris it doesn't, I'm just suggesting that there's some edge cases so that we're thorough with a |
Gotcha. Yes, it certainly warrants a 'use with caution' note when it's documented. |
One way to optimise scrolling it to delay the execution a bit like recommended on MDN |
I did some crude tests to see if scroll events were firing faster than 60fps, and it seemed like they weren't. But my tests weren't exhaustive. If the events really can fire faster than you'd get with rAF-based throttling, and the throttling doesn't introduce any kind of lag, then yeah, I agree that we should do that |
How did you checked that? Could you check with variables, which you do not output, but check in Debugger (against timestamps, maybe?). |
It was pretty crude, I just added a scroll listener that incremented a count, waggled the trackpad aggressively, counted to 10, then checked the count was below 600. My hypothesis was that browsers would throttle the event. Definitely worth some more scientific testing |
Here's a pen: http://codepen.io/PaulBGD/pen/NpXQyO Edge, Chromium, and Firefox all tested 60/second for me. |
The performance tab of Dev Tools (Firefox) showed an average duration of 0.05s per DOM event for me (you can filter everything out except those to clean the chart). Thanks for the Pen, @PaulBGD. Have you considered the impact of writing the DOM on the measurement? |
@Ryuno-Ki Well since it's not measuring performance and purely how often the scroll event is called, I don't think writing to the DOM matters. |
Thanks for the pen @PaulBGD, that's useful info. Looks like throttling is unnecessary. I've released the first version of |
@Rich-Harris I noticed in the comments here you added the possibility of supporting |
@ccampbell yes, I think it makes sense to have |
So my specific example is I have a component that is draggable around the window. You could almost handle that all at the component level, but what I tend to prefer doing is binding the There are cases when you are dragging something where the mouse could move outside of the bounds of the element (think about a video scrubber for example). So this way the dragging can continue even when you are not within the element being dragged anymore. I’m sure there are other (perhaps better) ways of doing what I’m doing, but I think it is a valid use case 😄 |
Minor comment but the colon prefix on: |
@ccampbell the way I've done that in the past is with a custom drag event: https://svelte.technology/repl?version=1.13.2&gist=114c93db7ff6131a64eaa2da239be9a4. But yeah, I reckon there probably are cases where you could use @bestguy I actually think the exact opposite is true — making it look like a normal component would be highly confusing. This way, you see straight away that it's something else (and if you're not already familiar with it, you're likely to go and read about it in the documentation, where we'll eventually add a section on meta-tags!) and you instinctively understand that it might not behave exactly like other components (for example, the fact that Less important, but it also makes validation much easier — we can determine at parse time, without having to wait until we've statically analysed your JavaScript, that this is a meta-tag that needs to obey certain rules (e.g. it has to be at the top level of the component template). |
That makes sense, thanks for the reasoning. I suppose the crux of it was the look of the colon prefix itself but minor, thanks. |
THIS IS SO USEFUL AND GENIUS!!! Can't tell you how many times I needed something like this when using Ractive, thank you new & awesome Svelte. |
I often find myself doing this sort of thing:
Lots of boilerplate, and it's easy to forget to remove the event listener when the component is destroyed.
Occurs to me we could have a special
<:Window/>
component (using the convention started with<:Self/>
), that behaved thusly:Or even this...
Or even this:
Lots of other events that would be useful —
on:offline
,on:hashchange
and so on. A lot of them could have their own bindings:bind:hash
,bind:online
,bind:scrollTop
(in fact that one could apply to regular elements, not justwindow
) and so on.Similarly:
The glorious thing about being fully compiled is we can kind of go nuts with this stuff without anyone needing to install extra components or ship code they don't need. The only cost we need to worry about is documentation and maintenance cost.
Interested to hear what people think of these ideas.
The text was updated successfully, but these errors were encountered: