3d-model-element
3d-model-element copied to clipboard
Content doesn't scroll in Safari browser
Safari doesn't update the scrollTop / scrollLeft properties of an element its any of its ancestors has overflow set to hidden.
Scroll offset is used to calculate the final render position of a 3D object so, in Safari, elements never scroll:
https://github.com/keithclark/3d-model-element/blob/2d16b02b14dfd761d376e3189ef419e497dfb4c9/src/model-element.js#L151-L152
Note: I've had reports that this is fixed in the latest Tech Preview.
What scroll are you watching? document.body? document.documentElement? An element in the DOM?
I've had this issue before affecting Chrome & Safari, but may not be strictly related: https://codepen.io/shshaw/pen/OXPKvr
What scroll are you watching? document.body? document.documentElement? An element in the DOM?
It could be one, or all of the above. For each <x-model> element the script walks up the DOM summing offsetLeft/scrollLeft and offsetTop/scrollTop until no parents are found.
And the goal here is to get an offset that you can use for positioning the camera? I wonder if getBoundingClientRect() to get the position of the element relative to viewport would instead be a better approach? This could greatly simplify your getTransformForElement function
The amount of scrolling that has been done of the viewport area (or any other scrollable element) is taken into account when computing the bounding rectangle. This means that the rectangle's boundary edges (top, left, bottom, and right) change their values every time the scrolling position changes (because their values are relative to the viewport and not absolute).
My first stab at resolving the position of an element was to use elem.getBoundingClientRect(). However, the resulting rectangle appears be the bounds of the element after any transforms have been applied, which makes the value unsuitable for use.
Here's a pen that shows what's going on.
You're right though, getBoundingClientRect does address the scrolling problem in Safari.
Oh yes. Apologies. I've run into that before and I thought I had a clever workaround for that, but it was looping through parents to get the offsetTop/offsetLeft as well! 😆
It seems like you'd want to render the element as if it's in the final transformed position (which would be easy with getBoundingClientRect), but you're looping through everything in this way because you're factoring in parent's perspective and perspective-origin and need to apply each matrix from the parent essentially?
you're looping through everything in this way because you're factoring in parent's perspective and perspective-origin and need to apply each matrix from the parent essentially?
Exactly. It's a shame there isn't an alternative to getBoudingClientRect that only works on the positioned properties.
🤔
I was going to suggest something like this:
let style = window.getComputedStyle(elem);
let transform = style.transform;
elem.style.transform = 'none';
let rect = elem.getBoundingClientRect();
let posX = rect.x;
let posY = rect.y;
elem.style.transform = transform;
But since this runs every frame for every 3D element, a more optimized approach than removing transforms or crawling all parents, perhaps with caching and/or checking for viewport visibility, would definitely be awesome.
That's definitely worth looking into. I'd be interested to see if that approach is faster or slower than walking the DOM.
I had a stab at this. The theory worked as expected but in practice there were too many side-effects to be useful.
-
Switching from the current transform to
noneand then back resulted in a big frame-rate drop - pages became very laggy. I suspect performing the multiplestyle.transformswitches are triggering layout trashing. -
getBoundingClientRect()doesn't return the desired value for an element if it's nested inside an ancestor with a transform applied to it. To get around that, I'd still have to walk up the DOM and do the transform style switching on each node, which would probably kill performance.
Nice idea though!
Yes, if the values could be cached and only had to be recalculated on resize then it wouldn't be a big deal, but anything that affects style running every frame will definitely drain perf. This is still on my mind; I'll reach out if any other ideas pop up.
I may have a partial solution for this. As part of a different issue I'm working on to support models nested in DOM elements with CSS overflow, I've had to split domUtils. getTransformForElement() into two functions:
The original getTransformForElement() will now only return a transform matrix for an element. The new function, getProjectionForElement() returns the computed perspective value, the accompanying projectionOrigin, the camera position and it's clipping bounds (which are passed to THREE.WebGLRenderer.setScissor())
The upshot of this is, the projection function calls element.getBoundingClientRect(), which fixes some demos that use scrolling in Safari. 😄
This appears to be limited to some DOM / CSSOM combinations so I need to do some testing to see what works and what doesn't.