说在前面
🎈鼠标控制元素旋转功能大家应该都不陌生了吧,今天我们一起来看看怎么编写一个
vue指令来实现元素旋转功能吧!
效果展示

体验地址
http://jyeontu.xyz/jvuewheel/#/JRotateView
实现思路
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、鼠标按下时的逻辑
-  计算中心点坐标:获取元素的 getBoundingClientRect来计算元素的中心点坐标centerX和centerY。
-  记录初始角度:使用 getAngle函数计算出鼠标相对于元素中心点的初始角度startingMouseAngle。
-  记录初始旋转:调用 getCurrentRotation函数获取并记录元素当前的旋转角度startingRotation。
-  添加鼠标事件监听:向 window添加mousemove和mouseup事件监听器,分别用于旋转效果和停止旋转。
-  设置 pointerEvents:设置el.style.pointerEvents = "none",以阻止鼠标事件在旋转区域上的其他交互。
5、旋转和停止旋转的函数
(1)停止旋转
function stopSpin() {
    window.removeEventListener("mousemove", spin);
    window.removeEventListener("mouseup", stopSpin);
    // 恢复旋转区域的鼠标事件
    el.style.pointerEvents = "auto";
}
当用户释放鼠标按钮时,调用stopSpin函数移除之前添加的mousemove和mouseup事件监听器,并恢复旋转区域的鼠标事件。
(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;
        }
    },
};
组件库
组件文档
目前该组件也已经收录到我的组件库,组件文档地址如下:
 http://jyeontu.xyz/jvuewheel/#/JRotateView
组件内容
组件库中还有许多好玩有趣的组件,如:
- 悬浮按钮
- 评论组件
- 词云
- 瀑布流照片容器
- 视频动态封面
- 3D轮播图
- web桌宠
- 贡献度面板
- 拖拽上传
- 自动补全输入框
- 图片滑块验证
等等……
组件库源码
组件库已开源到gitee,有兴趣的也可以到这里看看:https://gitee.com/zheng_yongtao/jyeontu-component-warehouse
觉得有帮助的可以点个star~
有什么问题或错误可以指出,欢迎pr~
有什么想要实现的组件或想法可以联系我~
公众号
关注公众号『前端也能这么有趣』,获取更多有趣内容。
发送『组件库』获取源码
说在后面
🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『
前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。



















