如何让onmouseenter与onmouseleave事件针对img元素而非其父容器div触发?
我完全懂你的困扰!你明明把onmouseenter和onmouseleave绑在了img上,但看起来事件像是跟着容器走——其实问题出在img的元素尺寸上:你给img设置了width: inherit; height: inherit;,这让它的元素范围和容器完全重合,哪怕object-fit: contain只让图片在中间显示,img元素本身还是占满了整个容器的空间。所以鼠标只要进入容器(哪怕是图片周围的空白),就会触发img的事件,看起来就像事件是绑在容器上一样。
而且你还不能去掉容器的width: 100%,也不能改img的object-fit: contain,那咱们就从「让img的交互区域只匹配图片实际可见部分」入手,给你两个可行的方案:
方案1:用动态CSS裁剪img的交互区域(推荐)
我们可以用clip-path属性,根据图片的原始比例和容器的比例,动态裁剪img的交互区域,让它只覆盖图片实际显示的部分。这样不需要修改事件逻辑,直接让事件只在图片可见区域触发:
完整代码示例
function mouse_entered() { console.log("mouse entered!") } function mouse_left() { console.log("mouse left!") } // 页面加载后动态设置clip-path,适配图片实际显示区域 window.addEventListener('load', function() { const img = document.querySelector('.image'); const imgNaturalWidth = img.naturalWidth; const imgNaturalHeight = img.naturalHeight; const imgRatio = imgNaturalWidth / imgNaturalHeight; const container = img.parentElement; const containerRatio = container.clientWidth / container.clientHeight; let clipPath; if (imgRatio > containerRatio) { // 图片比容器宽,显示时上下有空白,裁剪掉上下无效区域 const visibleHeightRatio = (container.clientHeight / container.clientWidth) * imgRatio; const topBottomInset = (1 - visibleHeightRatio) / 2 * 100; clipPath = `inset(${topBottomInset}% 0 ${topBottomInset}% 0)`; } else { // 图片比容器高,显示时左右有空白,裁剪掉左右无效区域 const visibleWidthRatio = imgRatio / (container.clientWidth / container.clientHeight); const leftRightInset = (1 - visibleWidthRatio) / 2 * 100; clipPath = `inset(0 ${leftRightInset}% 0 ${leftRightInset}%)`; } img.style.clipPath = clipPath; });
.image-container { width: 100%; height: 100vh; } .image { width: inherit; height: inherit; object-fit: contain; }
<div class="image-container"> <img src="https://lh3.googleusercontent.com/-s05ILbxi6YA/AAAAAAAAAAI/AAAAAAAAACc/yAd6W8jNSBI/photo.jpg?sz=256" class="image" onmouseenter="mouse_entered()" onmouseleave="mouse_left()" > </div>
这个方法的好处是,完全保留了你原来的布局设置(容器的width:100%和img的object-fit:contain都不用改),只是通过clip-path把img的交互区域限制在了图片实际显示的范围内。
方案2:在事件处理函数中判断鼠标是否在图片可见区域内
如果不想用clip-path,也可以在事件触发时,通过JavaScript计算图片的实际显示区域,判断鼠标是否真的在图片上:
function mouse_entered(e) { const img = e.target; const rect = img.getBoundingClientRect(); const imgNaturalWidth = img.naturalWidth; const imgNaturalHeight = img.naturalHeight; // 计算图片实际显示的宽高和位置 const containerWidth = rect.width; const containerHeight = rect.height; const imgRatio = imgNaturalWidth / imgNaturalHeight; const containerRatio = containerWidth / containerHeight; let displayWidth, displayHeight; if (imgRatio > containerRatio) { displayWidth = containerWidth; displayHeight = containerWidth / imgRatio; } else { displayHeight = containerHeight; displayWidth = containerHeight * imgRatio; } const imgLeft = rect.left + (containerWidth - displayWidth) / 2; const imgTop = rect.top + (containerHeight - displayHeight) / 2; const imgRight = imgLeft + displayWidth; const imgBottom = imgTop + displayHeight; // 只有当鼠标在图片可见区域内时才执行逻辑 if (e.clientX >= imgLeft && e.clientX <= imgRight && e.clientY >= imgTop && e.clientY <= imgBottom) { console.log("mouse entered the actual image!"); } } function mouse_left(e) { const img = e.target; const rect = img.getBoundingClientRect(); const imgNaturalWidth = img.naturalWidth; const imgNaturalHeight = img.naturalHeight; const containerWidth = rect.width; const containerHeight = rect.height; const imgRatio = imgNaturalWidth / imgNaturalHeight; const containerRatio = containerWidth / containerHeight; let displayWidth, displayHeight; if (imgRatio > containerRatio) { displayWidth = containerWidth; displayHeight = containerWidth / imgRatio; } else { displayHeight = containerHeight; displayWidth = containerHeight * imgRatio; } const imgLeft = rect.left + (containerWidth - displayWidth) / 2; const imgTop = rect.top + (containerHeight - displayHeight) / 2; const imgRight = imgLeft + displayWidth; const imgBottom = imgTop + displayHeight; // 只有当鼠标离开图片可见区域时才执行逻辑 if (!(e.clientX >= imgLeft && e.clientX <= imgRight && e.clientY >= imgTop && e.clientY <= imgBottom)) { console.log("mouse left the actual image!"); } }
不过这个方法有个小瑕疵:如果鼠标从图片区域直接移到img元素的空白区域(没离开容器),mouseleave不会触发——因为还在img元素范围内。如果要处理这种情况,还需要额外监听mousemove事件来判断鼠标是否从图片区域移到了空白区域,相对麻烦一些,所以更推荐方案1。
备注:内容来源于stack exchange,提问作者John Perry

