SVG遮罩动效

flyKot2025/10/29

效果预览

Discover the power of Tailwind CSS v4 with native CSS variables and container queries with advanced animations
The first rule of MRR Club is you do not talk about MRR Club. The second rule of MRR Club is you DO NOT talk about MRR Club.

分析

  • 遮罩效果的实现

使用 mask-image 引入svg,通过 mask-size 修改svg的大小,通过 mask-position 修改svg的位置

  • 过渡效果的实现

使用 transition: mask-size 0.4s ease-in-out, mask-position 0.2s linear; 实现过渡

如果想让鼠标移出盒子后svg消失,还可以写边界检测,到达边界后svg大小为0

HTML

    <div class="svg_mask_container">
        <div class="svg_mask_bottom">
            <div class="absolute_bg"></div>
            <div class="svg_mask_bottom_ctx">
                Discover the power of
                <span class="strong_blue_500">Tailwind CSS v4</span>
                with native CSS variables and container queries with
                <span class="strong_blue_500">advanced animations</span>
            </div>
        </div>
        <div class="svg_mask_top">
            The first rule of MRR Club is you do not talk about MRR Club. The second rule of MRR Club is you DO NOT talk
            about MRR Club.
        </div>
    </div>

CSS

body,
html {
  padding: 0;
  margin: 0;
  width: 100vw;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
}

.svg_mask_container {
  position: relative;
  height: 100%;
  width: 100%;
  box-sizing: border-box;
  --cx: 0;
  --cy: 0;
  --ms: 10px;
}

.svg_mask_top,
.svg_mask_bottom {
  box-sizing: border-box;
  padding: 16px;
  font-size: 32px;
  font-weight: 500;
}

.svg_mask_top {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: #fff;
  color: #000;
}

.svg_mask_bottom {
  width: 100%;
  height: 100%;
  position: absolute;
  inset: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: #000;
  color: #fff;
  -webkit-mask-image: url(./mask.svg);
  mask-image: url(./mask.svg);
  mask-repeat: no-repeat;
  /*-webkit-mask-size: 10px;*/
  /*mask-size: 10px;*/
  mask-size: var(--ms);
  mask-position: var(--cx) var(--cy);
  z-index: 1;
  transform-origin: center;
  transition: mask-size 0.4s ease-in-out,mask-position 0.2s linear;
}

.absolute_bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.strong_blue_500 {
  color: #2b7fff;
}

JavaScript

const container = document.querySelector(".svg_mask_container")
const bottom = document.querySelector(".svg_mask_bottom")
const content = bottom.getBoundingClientRect()
const bottomRect = bottom.getBoundingClientRect();

window.addEventListener("mousemove", (e) => {
  if (container) {

    const winY = container.clientHeight;
    const winX = container.clientWidth;
    let targetX = 0;
    let targetY = 0;
    const target = e.target;

    // 计算目标位置
    if (Array.from(target.classList).includes("svg_mask_bottom_ctx") ||
      Array.from(target.parentElement.classList).includes("svg_mask_bottom_ctx")) {
      // 处理放大效果
      container.style.setProperty("--ms", "600px")
      targetX = e.clientX - bottomRect.left - 300; // 调整圆心的位置
      targetY = e.clientY - bottomRect.top - 300;
    } else {
      targetX = e.clientX - bottomRect.left - 5; // 调整圆心的位置
      targetY = e.clientY - bottomRect.top - 5;

      // 处理边界情况
      const near_border = targetX <= 10 || (winX - targetX) < 10 || targetY <= 10 || (winY - targetY) < 10;
      if (near_border) {
        container.style.setProperty("--ms", 0);
      } else {
        container.style.setProperty("--ms", "10px");
      }
    }

    // 更新圆心位置
    container.style.setProperty("--cx", (targetX + "px"));
    container.style.setProperty("--cy", (targetY + "px"));
  }
});