For a client we're building horizontally dragging rows of media items. Dragging horizontally using Framer-Motion works great, but I can't animate the x position on the click of a button.
This is the general idea:
This is the component as I currently have it (I removed style, etc. for brevity):
const HorizontalScroll = ({ children }) => {
const x = useMotionValue(0);
function onLeftClick() {
const xPos = x.get();
if (Math.round(xPos) === 0) {
return;
}
const newXPosition = xPos + 600;
x.set(newXPosition > 0 ? 0 : newXPosition);
}
function onRightClick() {
const xPos = x.get();
const newXPosition = xPos - 600;
x.set(newXPosition < -2000 ? -2000 : newXPosition);
}
return (
<>
<button
type="button"
onClick={onLeftClick}
>
Left
</button>
<motion.div
drag="x"
dragConstraints={{ left: -2000, right: 0 }}
initial={false}
style={{ width: 2000, x }}
>
{children}
</motion.div>
<button
type="button"
onClick={onRightClick}
>
Right
</button>
</>
);
};
So I do x.set()
when clicking either left or right on the arrows. Doing x.set()
works, but it isn't animated.
Instead of const x = useMotionValue(0)
I could use useSpring
or useTransform
, but that breaks the dragging behaviour.
So in short, I want to animate the x.set()
, but I've no idea how to do that. Anybody got an idea?
I finally posted my question and I find the answer to my own question in under an hour...
In case anyone has the same edge case question. What I came up with (and this is by no means the most elegant solution) is using useAnimation
. My component currently looks like this:
const translateXForElement = (element) => {
const transform = element.style.transform;
if (!transform || transform.indexOf('translateX(') < 0) {
return 0;
}
const extractTranslateX = transform.match(/translateX\((-?\d+)/);
return extractTranslateX && extractTranslateX.length === 2
? parseInt(extractTranslateX[1], 10)
: 0;
}
const HorizontalScroll = ({ children }) => {
const dragRef = useRef(null);
const animation = useAnimation();
function onLeftClick() {
const xPos = translateXForElement(dragRef.current);
const newXPosition = xPos + 600;
animation.start({
x: newXPosition > 0 ? 0 : newXPosition,
});
}
function onRightClick() {
const xPos = translateXForElement(dragRef.current);
const newXPosition = xPos - 600;
animation.start({
x: newXPosition < -2000 ? -2000 : newXPosition,
});
}
return (
<>
<button
type="button"
onClick={onLeftClick}
>
Left
</button>
<motion.div
drag="x"
dragConstraints={{ left: -2000, right: 0 }}
initial={false}
animate={animation}
style={{ width: 2000, x: 0, opacity: 1 }}
ref={dragRef}
>
{children}
</motion.div>
<button
type="button"
onClick={onRightClick}
>
Right
</button>
</>
);
};
I couldn't find a (nice) solution to retrieve the current translateX
for an element, so I did a regex on element.style.transform
for now. It's too bad useAnimation()
doesn't allow retrieving the x
(or I'm missing something).
Of course if you've anything to approve in my code, I would love to hear it!