元素旋转?一个vue指令搞定

267 阅读3分钟

说在前面

🎈元素旋转功能大家应该都不陌生了吧,今天我们一起来看看怎么编写一个vue指令来实现元素旋转功能吧!

效果展示

AI改图-JYeontu组件库 - Google Chrome 2024-05-01 05-34-53-720x387 (1).gif

体验地址

jyeontu.xyz/jvuewheel/#…

实现思路

1、自定义指令对象

export default {
  inserted(el, binding) {
    // ...
  }
};

这里定义了一个Vue自定义指令,并通过inserted钩子函数,在元素被插入到DOM时执行相关的逻辑。

2、变量声明

let startingMouseAngle = 0;
let startingRotation = 0;

声明了两个变量,startingMouseAngle用于存储鼠标按下时的位置角度,startingRotation用于存储元素初始的旋转角度。

3、事件监听器

(1)防止文本选择

el.addEventListener("selectstart", function (event) {
  event.preventDefault();
});

通过监听selectstart事件来防止用户在旋转区域内选中文本。

(2)鼠标按下事件

el.addEventListener("mousedown", function (event) {
  // ...
});

当用户在元素上按下鼠标时,会触发mousedown事件。

4、鼠标按下时的逻辑

  1. 计算中心点坐标:获取元素的getBoundingClientRect来计算元素的中心点坐标centerXcenterY

  2. 记录初始角度:使用getAngle函数计算出鼠标相对于元素中心点的初始角度startingMouseAngle

  3. 记录初始旋转:调用getCurrentRotation函数获取并记录元素当前的旋转角度startingRotation

  4. 添加鼠标事件监听:向window添加mousemovemouseup事件监听器,分别用于旋转效果和停止旋转。

  5. 设置pointerEvents:设置el.style.pointerEvents = "none",以阻止鼠标事件在旋转区域上的其他交互。

5、旋转和停止旋转的函数

(1)停止旋转

function stopSpin() {
    window.removeEventListener("mousemove", spin);
    window.removeEventListener("mouseup", stopSpin);
    // 恢复旋转区域的鼠标事件
    el.style.pointerEvents = "auto";
}

当用户释放鼠标按钮时,调用stopSpin函数移除之前添加的mousemovemouseup事件监听器,并恢复旋转区域的鼠标事件。

(2)旋转逻辑

function spin(event) {
    const rect = el.getBoundingClientRect();
    const centerX = rect.left + rect.width / 2;
    const centerY = rect.top + rect.height / 2;
    const currentMouseAngle = getAngle(
        centerX,
        centerY,
        event.clientX,
        event.clientY
    );
    const deltaMouseAngle = currentMouseAngle - startingMouseAngle;
    let newRotation = startingRotation + deltaMouseAngle;
    newRotation = normalizeRotation(newRotation);
    el.style.transform = `rotate(${newRotation}deg)`;
}

spin函数计算当前鼠标位置与起始位置的夹角,然后更新元素的旋转角度。

6、旋转角度规范化

function normalizeRotation(rotation) {
    if (rotation >= 0) {
        return rotation % 360;
    } else {
        return (rotation % 360) + 360;
    }
}

normalizeRotation函数确保旋转角度在0到360度之间循环。

7、获取鼠标角度

function getAngle(centerX, centerY, mouseX, mouseY) {
    return (
        Math.atan2(mouseY - centerY, mouseX - centerX) * (180 / Math.PI)
    );
}

getAngle函数使用Math.atan2计算鼠标相对于元素中心点的角度。

8、获取当前旋转角度

function getCurrentRotation() {
    const transformStyle = window
        .getComputedStyle(el)
        .getPropertyValue("transform");
    const matrix = new DOMMatrixReadOnly(transformStyle);
    const angle = Math.acos(matrix.a) * (180 / Math.PI);
    return matrix.b < 0 ? -angle : angle;
}

getCurrentRotation函数尝试从元素的CSS变换属性中解析出当前的旋转角度。这里使用了DOMMatrixReadOnly,但请注意,这个API可能不被所有浏览器支持,且从CSS变换中解析角度可能不是最直接的方法。

完整代码

export default {
    inserted(el, binding) {
        let startingMouseAngle = 0;
        let startingRotation = 0;

        el.addEventListener("selectstart", function (event) {
            event.preventDefault();
        });
        el.addEventListener("mousedown", function (event) {
            const rect = el.getBoundingClientRect();
            const centerX = rect.left + rect.width / 2;
            const centerY = rect.top + rect.height / 2;
            startingMouseAngle = getAngle(
                centerX,
                centerY,
                event.clientX,
                event.clientY
            );
            startingRotation = getCurrentRotation();

            window.addEventListener("mousemove", spin);
            window.addEventListener("mouseup", stopSpin);
            // 阻止元素的拖动事件
            el.style.pointerEvents = "none";
        });

        function stopSpin() {
            window.removeEventListener("mousemove", spin);
            window.removeEventListener("mouseup", stopSpin);
            // 恢复旋转区域的鼠标事件
            el.style.pointerEvents = "auto";
        }

        function spin(event) {
            const rect = el.getBoundingClientRect();
            const centerX = rect.left + rect.width / 2;
            const centerY = rect.top + rect.height / 2;
            const currentMouseAngle = getAngle(
                centerX,
                centerY,
                event.clientX,
                event.clientY
            );
            const deltaMouseAngle = currentMouseAngle - startingMouseAngle;
            let newRotation = startingRotation + deltaMouseAngle;
            newRotation = normalizeRotation(newRotation);
            el.style.transform = `rotate(${newRotation}deg)`;
        }

        function normalizeRotation(rotation) {
            if (rotation >= 0) {
                return rotation % 360;
            } else {
                return (rotation % 360) + 360;
            }
        }

        function getAngle(centerX, centerY, mouseX, mouseY) {
            return (
                Math.atan2(mouseY - centerY, mouseX - centerX) * (180 / Math.PI)
            );
        }

        function getCurrentRotation() {
            const transformStyle = window
                .getComputedStyle(el)
                .getPropertyValue("transform");
            const matrix = new DOMMatrixReadOnly(transformStyle);
            const angle = Math.acos(matrix.a) * (180 / Math.PI);
            return matrix.b < 0 ? -angle : angle;
        }
    },
};

组件库

组件文档

目前该组件也已经收录到我的组件库,组件文档地址如下: jyeontu.xyz/jvuewheel/#…

组件内容

组件库中还有许多好玩有趣的组件,如:

  • 悬浮按钮
  • 评论组件
  • 词云
  • 瀑布流照片容器
  • 视频动态封面
  • 3D轮播图
  • web桌宠
  • 贡献度面板
  • 拖拽上传
  • 自动补全输入框
  • 图片滑块验证

等等……

组件库源码

组件库已开源到gitee,有兴趣的也可以到这里看看:gitee.com/zheng_yongt…

觉得有帮助的可以点个star~

有什么问题或错误可以指出,欢迎pr~

有什么想要实现的组件或想法可以联系我~

公众号

关注公众号『前端也能这么有趣』,获取更多有趣内容。

发送『组件库』获取源码

说在后面

🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。