I have three <Html> screens in my scene which are inside of a <ScrollControls>. I don’t include them in a <Scroll> because i actually just want to change the camera position on scrolling and am using useScroll for that. The code works.
I now wanted to implement a snapping mechanism so that if i am scrolling in between two screens, after some period of time (instantly would even be better!), the application automatically scrolls to the nearest screen.
theta() is calculating my angle which i then pass to a function for rotating the HTML screens relative to an anchor.
After some debugging i realised that my values are actually fine and everything is working as it should. I think the problem is that setting data.offset to a value is not scrolling the scrollbar. Is there a solution for this using <ScrollControls/>?
And some further questions: Is there an easier way to achieve this? And if it isn’t solvable using <ScrollControls/> because of the described limitation, how can i solve this (using r3f)?
function Screens() {
const data = useScroll();
const animPoints = Array(screenDescr.length)
.fill()
.map((_,i) => i * 2 * Math.PI / screenDescr.length)
const size = 2 * Math.PI * (screenDescr.length-1) / screenDescr.length;
let lastOffset = data.offset;
let lastTime = 0;
let time = 0;
useFrame((state, delta) => {
time += delta;
const theta = () => {
const normal = data.offset * size;
if( time - lastTime >= 4 ) {
if(lastOffset !== data.offset){
lastTime = time;
lastOffset = data.offset;
return normal;
}
let closest;
let closestPoint;
animPoints.forEach( animPoint => {
const offsetAngle = size * data.offset;
const diff = Math.abs(animPoint - offsetAngle);
if(closest == null || diff < closest){
closest = diff;
closestPoint = animPoint;
}
})
data.offset = closestPoint / size;
return closestPoint;
}
return normal;
}
rotateAboutPointImmutable(
state.camera,
new THREE.Vector3(0,0,0),
new THREE.Vector3(0,1,0),
theta(),
false,
new THREE.Vector3(0,0,5),
new THREE.Vector3(0,0,0)
)
})
return screenDescr.map((screen, i) => <Screen
key={i}
index={i}
underlyingHtml={screen.underlyingHtml}
{...screen}
/>
)
}
The changes made by the scrollLeft property isn’t used… Do you have any idea why? (And the code seems to be really computation-heavy, my mac book is literally dying on execution )
with scrollLeft i mean the css property on the dom scrollcontainer. i believe you get the scroll container in the useScroll data, i think it was called “el” but either way, it is a div which is set to overflow-x/y: scroll. scrollLeft on the ScrollControls component does nothing.
Hey, this worked for me too! Thanks so much! I am using ScrollControls in a React-three-fiber project with drei components. I have an animation set up with Theatre.js, and I was trying to create “anchors” to certain points in the overall animation by changing the offset property of useScroll() (i.e. data = useScroll(); data.offset = /* set value */). This was unsuccessful, and instead the above worked by grabbing the underlying scrollTop value. Basic outline of my code:
import { ScrollControls, useScroll } from “@react-three/drei”;
import { editable as e, SheetProvider, PerspectiveCamera, useCurrentSheet } from ‘@theatre/r3f’
scroll.el.onscroll = () => {
console.log(“canvas element scrolled”)
console.log(scroll.el.scrollTop)
}
// Set scroll position to anchor point
scroll.el.scrollTop = 30000
// Use to recalculate sequence position for Theatre.js:
useFrame(() => {
// the length of my Theatre.js sequence
const sequenceLength = val(sheet.sequence.pointer.length);
// update the "position" of the playhead in the sequence, as a fraction of its whole length
sheet.sequence.position = scroll.offset * sequenceLength;