需求分析
要求做一个如图所示的放大镜,鼠标随动,右键开启关闭,含过渡动画。
大体的思路就是绑定鼠标事件,动态地修改background-position,难点在于background-position的计算要考虑周全。
开关动画部分我们选择用transform: scale来实现,相对于改width / height而言,transform做过渡时可以结合transform-origin解决圆心位移到左上角的问题。
性能优化方面,采用了一个简易节流函数对resize和mousemove事件进行了节流。另外,动态修改CSS时也视情况尽量做到了最简,保证不需要的修改不做。
细节体验方面,因为放大镜元素的cursor设为了none,故选择在放大镜关闭时将元素整体位移至视窗外(第二象限),解决了关闭放大镜后鼠标消失的问题。
静态CSS部分
首先需要做的是给一个fixed定位,让我们的放大镜脱离文档流,并相对于窗口进行定位。
放大镜有圆形边框,这里用border-radius:50%,并给内外侧都套上box-shadow。
transform: scale需要先给一个初始的0,否则之后在JS中初始化会触发一次动画。
开关时的过渡动画用transition: transform即可。
其他如border,width,height,transform,left,top,background-position等属性则全部通过JS操作。
CSS代码如下:
#magnifier {
position: fixed;
border-radius: 50%;
box-shadow: 0 0 4px 2px rgba(51, 51, 51, 0.2) inset,
0 0 4px 2px rgba(51, 51, 51, 0.3);
background-image: url("1.jpeg");
background-repeat: no-repeat;
transform: scale(0, 0);
transition: transform 0.15s ease-out;
cursor: none;
}
JS部分
持续监听鼠标右键:contextmenu事件,用一个变量控制放大镜的开关,根据该变量的值来决定添加或移除mousemove监听器。
先定义配置项常量,RAD代表放大镜的半径,FPS设置每秒最大的事件触发次数(用于节流),BORDER为放大镜边框的宽度。日后如果要做成组件,这些都可以当做可配置的props传入。
const RAD = 200,
FPS = 125,
BORDER = 8;
再定义两个全局变量,toggled记录当前放大镜的开闭状态,mag为放大镜对应的DOM元素。
var toggled = false,
mag = null;
窗口加载时监听contextmenu,并初始化一些样式。
window.onload = function() {
document.addEventListener("contextmenu", handleRightClick);
mag = document.getElementById("magnifier");
Object.assign(mag.style, {
width: `${
RAD * 2}px`,
height: `${
RAD * 2}px`,
backgroundSize: `${
window.innerWidth}px ${
window.innerHeight}px`,
border: `${
BORDER}px solid #fff`
});
};
下面是contextmenu回调的实现。要注意backgroundPosition的值是鼠标坐标取反,并且要把放大镜大小和边框宽度一并计算在内。
function handleRightClick(e) {
e.preventDefault();
if (toggled) {
// toggle off
document.removeEventListener("mousemove", handleMouseMove);
Object.assign(mag.style, {
left: `${
-RAD * 2}px`,
top: `${
-RAD *