说在前面
🎈鼠标控制元素旋转功能大家应该都不陌生了吧,今天我们一起来看看怎么编写一个
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,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『
前端也能这么有趣
』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。