You need to enable JavaScript to run this app. 如何通过键盘输入实现Framer Motion元素位置的平滑移动
优惠活动
大模型
产品
解决方案
定价
更多

如何通过键盘输入实现Framer Motion元素位置的平滑移动

问题根源

位移不平滑的核心原因是MotionValue API使用错误:

  • 你调用的x.set()是即时更新值的方法,不会生成任何过渡动画,元素会直接跳转到目标位置,因此观感是生硬的跳跃而非平滑滑动。
  • 附带的潜在问题:
    • 全局绑定的keydown事件未在组件卸载时清理,长期运行会造成内存泄漏
    • 同时对x和xPos执行set操作,若二者为独立MotionValue会出现同步偏差,属于冗余逻辑
    • 未仅针对方向键阻止默认行为,会干扰页面原生滚动等交互
修正方案

核心调整是将x.set()替换为x.animate(),该方法会自动生成从当前值到目标值的过渡动画,同时配套优化事件监听逻辑。

调整后代码示例

import { useEffect } from "react";
import { motion, useMotionValue } from "framer-motion";

function Box() {
  // 仅保留一个用于控制元素位移的MotionValue即可
  const x = useMotionValue(0);
  // 若xPos是用于同步位移值做其他业务逻辑,可通过监听x的变化自动同步,无需手动赋值
  const xPos = useMotionValue(0);

  useEffect(() => {
    // 监听x变化自动同步xPos,避免不同步
    const unsubscribe = x.on("change", (latest) => {
      xPos.set(latest);
    });
    return unsubscribe;
  }, [x]);

  useEffect(() => {
    const handleKeyDown = (event) => {
      const key = event.key;
      // 仅处理左右方向键,其他按键不触发阻止默认行为
      if (key !== "ArrowLeft" && key !== "ArrowRight") return;
      event.preventDefault();

      const currentX = x.get();
      const factor = 100;
      let targetX = currentX;

      if (key === "ArrowLeft") targetX = currentX - factor;
      if (key === "ArrowRight") targetX = currentX + factor;

      // 先停止当前正在运行的动画,避免连续按键时动画叠加冲突
      x.stop();
      // 用animate替代set,第二个参数可自定义动画效果
      x.animate(targetX, {
        type: "spring",
        stiffness: 260, // 弹簧刚度,数值越大动画越快
        damping: 20 // 阻尼,数值越大越不容易回弹
      });
    };

    window.addEventListener("keydown", handleKeyDown);
    // 组件卸载时清理事件监听,避免内存泄漏
    return () => {
      window.removeEventListener("keydown", handleKeyDown);
    };
  }, []);

  return (
    <motion.div className="example-container" style={{ background }}>
      <motion.div
        className="box"
        style={{ x }}
        drag="x"
        dragConstraints={{ left: 0, right: 0 }}
        onDragEnd={(event, info) => OnEndDrag(x.get())}
      ></motion.div>
    </motion.div>
  );
}

关键API说明

  • x.animate(targetValue, animationOptions):MotionValue提供的动画方法,相比set()会自动生成中间过渡帧,支持两种常用动画模式:
    • 弹簧效果:如上代码示例的type: "spring",适合做带自然回弹的位移
    • 匀速过渡:若需要线性的滑动效果,可替换为如下配置
      x.animate(targetX, {
        type: "tween",
        duration: 0.3,
        ease: "easeOut"
      })
      
可选优化
  • 若需要支持长按方向键连续平滑移动,可在keydown事件中记录按键状态,搭配定时器实现连续触发目标值更新,同时在keyup事件中清除定时器。
  • 若需要统一拖拽和键盘移动的动画体感,可将拖拽的过渡效果配置为和x.animate一致的参数。

内容的提问来源于stack exchange,提问作者Datguypotato

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.25 13:36:08