如何实现旋转时钟指针的drop-shadow始终朝下
让时钟指针的投影始终朝下的解决方案
问题的核心在于:drop-shadow滤镜是跟随元素的变换(比如旋转)一起生效的,所以指针旋转时影子也会跟着转,没法单独控制影子的方向。要解决这个问题,我们需要把指针的旋转和影子的方向分离开,下面提供两种实用的实现方案:
方案一:使用独立的影子元素反向旋转
我们可以给每个指针添加一个对应的影子元素(复制指针图片),让指针正常旋转,同时让影子元素以相反的角度旋转,抵消指针的旋转效果,再通过偏移和模糊模拟朝下的投影。
步骤1:修改HTML结构
给每个指针增加一个影子元素:
<div class="hand-container"> <img src="media/hour-hand.png" class="hourHand hand" /> <img src="media/hour-hand.png" class="hourHand-shadow hand-shadow" /> </div> <div class="hand-container"> <img src="media/minute-hand.png" class="minuteHand hand" /> <img src="media/minute-hand.png" class="minuteHand-shadow hand-shadow" /> </div> <div class="hand-container"> <img src="media/second-hand.png" class="secondHand hand" /> <img src="media/second-hand.png" class="secondHand-shadow hand-shadow" /> </div> <div class="hand-container small-second-container"> <img src="media/small-second-hand.png" class="smallSecondHand hand" /> <img src="media/small-second-hand.png" class="smallSecondHand-shadow hand-shadow" /> </div>
步骤2:编写基础CSS
设置容器、指针和影子的样式,让影子默认朝下:
.hand-container { position: absolute; top: 50%; left: 50%; } .small-second-container { left: calc(50% - 10vmin + 2px); } .hand { position: relative; z-index: 2; height: 30vmin; transform: translate(-50%, -50%); } .smallSecondHand { height: 12vmin; } .hand-shadow { position: absolute; top: 50%; left: 50%; z-index: 1; height: inherit; opacity: 0.3; filter: blur(2px); /* 调整translate的Y值,让影子朝下偏移 */ transform: translate(-50%, -38%) rotate(0deg); } .smallSecondHand-shadow { height: 12vmin; }
步骤3:更新JavaScript逻辑
在时钟更新函数中,同时给指针和影子设置旋转角度(影子角度为指针角度的负值):
setInterval(clock, 10); function clock() { let dt = new Date(), hr = dt.getHours(), min = dt.getMinutes(), sec = dt.getSeconds(), msec = dt.getMilliseconds(), hrDeg = 55 + (hr * 30) + (min * 30/60), minDeg = -51.7 + (min * 6) + (sec * 6/60), secDeg = (sec * 6) + (6 * msec/1000), // 获取指针和影子元素 hrHand = document.querySelector('.hourHand'), minHand = document.querySelector('.minuteHand'), secHand = document.querySelector('.smallSecondHand'), hrShadow = document.querySelector('.hourHand-shadow'), minShadow = document.querySelector('.minuteHand-shadow'), secShadow = document.querySelector('.smallSecondHand-shadow'); // 更新指针旋转 hrHand.style.transform = `translate(-50%, -50%) rotate(${hrDeg}deg)`; minHand.style.transform = `translate(-50%, -50%) rotate(${minDeg}deg)`; secHand.style.transform = `translate(-50%, -50%) rotate(${secDeg}deg)`; // 更新影子旋转(反向抵消指针的旋转) hrShadow.style.transform = `translate(-50%, -38%) rotate(${-hrDeg}deg)`; minShadow.style.transform = `translate(-50%, -38%) rotate(${-minDeg}deg)`; secShadow.style.transform = `translate(-50%, -38%) rotate(${-secDeg}deg)`; }
方案二:使用CSS伪元素 + 自定义属性(更简洁)
如果不想增加额外的HTML元素,可以用伪元素生成影子,结合CSS自定义属性来管理旋转角度,让伪元素自动反向旋转。
步骤1:修改HTML结构
用容器包裹每个指针:
<div class="hand-wrapper hour-wrapper"> <img src="media/hour-hand.png" class="hourHand hand" /> </div> <div class="hand-wrapper minute-wrapper"> <img src="media/minute-hand.png" class="minuteHand hand" /> </div> <div class="hand-wrapper second-wrapper"> <img src="media/second-hand.png" class="secondHand hand" /> </div> <div class="hand-wrapper small-second-wrapper"> <img src="media/small-second-hand.png" class="smallSecondHand hand" /> </div>
步骤2:编写CSS
利用伪元素生成影子,通过自定义属性控制旋转:
.hand-wrapper { position: absolute; top: 50%; left: 50%; --rotate: 0deg; /* 容器旋转带动指针 */ transform: translate(-50%, -50%) rotate(var(--rotate)); } .small-second-wrapper { left: calc(50% - 10vmin + 2px); } .hand { position: relative; z-index: 2; height: 30vmin; transform: translate(-50%, -50%); } .smallSecondHand { height: 12vmin; } /* 伪元素作为影子 */ .hand-wrapper::after { content: ''; position: absolute; top: 50%; left: 50%; z-index: -1; height: inherit; width: auto; opacity: 0.3; filter: blur(2px); /* 反向旋转抵消容器的旋转,保持影子朝下 */ transform: translate(-50%, -38%) rotate(calc(-1 * var(--rotate))); } /* 给每个伪元素设置对应指针的图片 */ .hour-wrapper::after { background-image: url('media/hour-hand.png'); } .minute-wrapper::after { background-image: url('media/minute-hand.png'); } .second-wrapper::after { background-image: url('media/second-hand.png'); } .small-second-wrapper::after { background-image: url('media/small-second-hand.png'); height: 12vmin; }
步骤3:更新JavaScript逻辑
只需要更新容器的自定义属性即可:
setInterval(clock, 10); function clock() { let dt = new Date(), hr = dt.getHours(), min = dt.getMinutes(), sec = dt.getSeconds(), msec = dt.getMilliseconds(), hrDeg = 55 + (hr * 30) + (min * 30/60), minDeg = -51.7 + (min * 6) + (sec * 6/60), secDeg = (sec * 6) + (6 * msec/1000), // 获取容器元素 hrWrapper = document.querySelector('.hour-wrapper'), minWrapper = document.querySelector('.minute-wrapper'), secWrapper = document.querySelector('.small-second-wrapper'); // 更新容器的旋转角度 hrWrapper.style.setProperty('--rotate', `${hrDeg}deg`); minWrapper.style.setProperty('--rotate', `${minDeg}deg`); secWrapper.style.setProperty('--rotate', `${secDeg}deg`); }
原理说明
两种方案的核心都是分离指针旋转和影子方向:让指针正常旋转来指示时间,同时让影子元素(或伪元素)以相反的角度旋转,抵消指针的旋转效果,再通过Y轴偏移和模糊滤镜模拟出始终朝下的投影效果。这样就能避免drop-shadow跟随元素旋转的问题。
内容的提问来源于stack exchange,提问作者TheAndersMan
相关产品推荐
相关产品推荐

