🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

滑鼠滑動切割效果

滑鼠滑動切割效果

滑鼠滑動切割效果

效果預覽

滑鼠滑動切割.gif

體驗AI程式碼助手

一、理論基礎

將兩張相同的圖片 position:absolute 定位在一起,透過 clip-path:polygon() 這個 CSS 屬性將元素裁剪。第一張圖片保留上半部分,第二張圖片保留下半部分,將上半部分向上移動,下半部分向下移動,形成視覺的切割效果。

二、頁面佈局

front 放置第一張圖片,back 放置第二張圖片,canvas 用來繪製切割線。三個元素定位在相同的位置,保持 canvas 顯示在最上面。

<div class="box">
  <div class="block back"></div>
  <div class="block front"></div>
  <canvas width="400" height="600" class="block canvas"></canvas>
</div>

<style>
  html,
  body {
    margin: 0;
    padding: 0;
  }
  .box {
    width: 100vw;
    height: 100vh;
    position: relative;
  }
  .block {
    width: 400px;
    height: 600px;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
  }
  .back {
    background-color: #f00;
  }
  .front {
    background-color: #f00;
  }
  .canvas {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
  }
</style>

三、操作邏輯

初始化

// 外層盒子
const box = document.querySelector(".box");
// canvas 用來繪製切割線
const canvas = document.querySelector(".canvas");
// 第一個元素
const front = document.querySelector(".front");
// 第二個元素
const back = document.querySelector(".back");

// canvas 上下文對象
const ctx = canvas.getContext("2d");
// 是否開始繪製
let startDraw = false;
// 切割的起始點坐標
let clipPoint1 = [0, 0];
// 切割終點坐標
let clipPoint2 = [0, 0];

監聽滑鼠按下

對 box 監聽滑鼠按下事件,如果是在 canvas 中按下的則不處理。滑鼠移動進入 canvas 時記錄切割的起始坐標,同時開始繪製切割線。滑鼠在 canvas 中移動,實時繪製切割線。滑鼠移出 canvas 時記錄切割的終點坐標,同時停止繪製切割線。

// 監聽滑鼠按下事件
box.addEventListener("mousedown", (e) => {
  // 如果滑鼠不是在 canvas 外按下的則不處理
  if (e.target == canvas) {
    return;
  }
  // 開始繪製切割線
  startDraw = true;
  // 初始化坐標
  clipPoint1 = [0, 0];
  clipPoint2 = [0, 0];
  // 監聽滑鼠進入 canvas 事件,獲取切割起始坐標,開始繪製切割線
  canvas.addEventListener("mouseenter", (e) => {
    clipPoint1 = [e.offsetX, e.offsetY];
    ctx.beginPath();
  });
  // 滑鼠在canvas中移動,繪製切割線
  canvas.addEventListener("mousemove", (e) => {
    if (startDraw) {
      ctx.lineTo(e.offsetX, e.offsetY);
      ctx.stroke();
    }
  });
  // 滑鼠離開canvas,獲取切割終點坐標,停止繪製切割線
  canvas.addEventListener("mouseleave", (e) => {
    clipPoint2 = [e.offsetX, e.offsetY];
    startDraw = false;
  });
});

監聽滑鼠彈起

在滑鼠彈起時,根據起始和終點坐標對 front 和 back 設置 clip-path 切割元素。然後獲取元素的 top 值,設置動畫,front 向上移動,back 向下移動。

// 監聽滑鼠彈起事件
box.addEventListener("mouseup", (e) => {
  // 清除切割線
  ctx.clearRect(0, 0, 600, 800);
  // 停止繪製切割線
  startDraw = false;
  // 如果滑鼠水平滑動的距離不超過390,則不會切割元素
  // 元素寬度400,取390保持冗餘
  if (Math.abs(clipPoint2[0] - clipPoint1[0]) < 390) {
    return;
  }
  // 切割點Y坐標
  let point1Y;
  let point2Y;
  // 無論是從左往右切割還是從右往左,保持 point1Y為左邊Y坐標,point2Y右邊Y坐標
  if (clipPoint2[0] - clipPoint1[0] < 0) {
    point1Y = clipPoint2[1];
    point2Y = clipPoint1[1];
  } else {
    point1Y = clipPoint1[1];
    point2Y = clipPoint2[1];
  }
  // 設置裁剪路徑
  clipPath1 = `polygon(0 0, 400px 0, 400px ${point2Y}px, 0 ${point1Y}px)`;
  clipPath2 = `polygon(0 ${point1Y}px, 400px ${point2Y}px, 400px 600px, 0 600px)`;
  // 對元素裁剪
  front.style.clipPath = clipPath1;
  back.style.clipPath = clipPath2;
  // 獲取元素的 top 屬性值
  let frontTop, backTop;
  frontTop = backTop = parseFloat(getComputedStyle(front).top);
  // 移動的距離
  let num = 0;
  // 動畫id
  let requestId = null;
  // 切割後移動動畫
  function move() {
    num += 1;
    frontTop -= num;
    backTop += num;
    // 第一張向上移動
    front.style.top = frontTop + "px";
    // 第二張向下移動
    back.style.top = backTop + "px";
    requestId = requestAnimationFrame(move);
    // 移動距離大於5則結束移動
    if (num >= 5) {
      cancelAnimationFrame(requestId);
    }
  }
  requestId = requestAnimationFrame(move);
});

完整程式碼


<!DOCTYPE html>
<html lang="zh_CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>滑動切割</title>

    <style>
      html,
      body {
        margin: 0;
        padding: 0;
      }
      .box {
        width: 100vw;
        height: 100vh;
        position: relative;
      }
      .block {
        width: 400px;
        height: 600px;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
      }
      .back {
        background-color: #f00;
      }
      .front {
        background-color: #f00;
      }
      .canvas {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="block back"></div>
      <div class="block front"></div>
      <canvas width="400" height="600" class="block canvas"></canvas>
    </div>
    <script>
      // 外層盒子
      const box = document.querySelector(".box");
      // canvas 用來繪製切割線
      const canvas = document.querySelector(".canvas");
      // 第一個元素
      const front = document.querySelector(".front");
      // 第二個元素
      const back = document.querySelector(".back");

      // canvas 上下文對象
      const ctx = canvas.getContext("2d");
      // 是否開始繪製
      let startDraw = false;
      // 切割的起始點坐標
      let clipPoint1 = [0, 0];
      // 切割終點坐標
      let clipPoint2 = [0, 0];

      // 監聽滑鼠按下事件
      box.addEventListener("mousedown", (e) => {
        // 如果滑鼠不是在 canvas 外按下的則不處理
        if (e.target == canvas) {
          return;
        }
        // 開始繪製切割線
        startDraw = true;
        // 初始化坐標
        clipPoint1 = [0, 0];
        clipPoint2 = [0, 0];
        // 監聽滑鼠進入 canvas 事件,獲取切割起始坐標,開始繪製切割線
        canvas.addEventListener("mouseenter", (e) => {
          clipPoint1 = [e.offsetX, e.offsetY];
          ctx.beginPath();
        });
        // 滑鼠在canvas中移動,繪製切割線
        canvas.addEventListener("mousemove", (e) => {
          if (startDraw) {
            ctx.lineTo(e.offsetX, e.offsetY);
            ctx.stroke();
          }
        });
        // 滑鼠離開canvas,獲取切割終點坐標,停止繪製切割線
        canvas.addEventListener("mouseleave", (e) => {
          clipPoint2 = [e.offsetX, e.offsetY];
          startDraw = false;
        });
      });
      // 監聽滑鼠彈起事件
      box.addEventListener("mouseup", (e) => {
        // 清除切割線
        ctx.clearRect(0, 0, 600, 800);
        // 停止繪製切割線
        startDraw = false;
        // 如果滑鼠水平滑動的距離不超過390,則不會切割元素
        // 元素寬度400,取390保持冗餘
        if (Math.abs(clipPoint2[0] - clipPoint1[0]) < 390) {
          return;
        }
        // 切割點Y坐標
        let point1Y;
        let point2Y;
        // 無論是從左往右切割還是從右往左,保持 point1Y為左邊Y坐標,point2Y右邊Y坐標
        if (clipPoint2[0] - clipPoint1[0] < 0) {
          point1Y = clipPoint2[1];
          point2Y = clipPoint1[1];
        } else {
          point1Y = clipPoint1[1];
          point2Y = clipPoint2[1];
        }
        // 設置裁剪路徑
        clipPath1 = `polygon(0 0, 400px 0, 400px ${point2Y}px, 0 ${point1Y}px)`;
        clipPath2 = `polygon(0 ${point1Y}px, 400px ${point2Y}px, 400px 600px, 0 600px)`;
        // 對元素裁剪
        front.style.clipPath = clipPath1;
        back.style.clipPath = clipPath2;
        // 獲取元素的 top 屬性值
        let frontTop, backTop;
        frontTop = backTop = parseFloat(getComputedStyle(front).top);
        // 移動的距離
        let num = 0;
        // 動畫id
        let requestId = null;
        // 切割後移動動畫
        function move() {
          num += 1;
          frontTop -= num;
          backTop += num;
          // 第一張向上移動
          front.style.top = frontTop + "px";
          // 第二張向下移動
          back.style.top = backTop + "px";
          requestId = requestAnimationFrame(move);
          // 移動距離大於5則結束移動
          if (num >= 5) {
            cancelAnimationFrame(requestId);
          }
        }
        requestId = requestAnimationFrame(move);
      });
    </script>
  </body>
</html>

---

原文出處:https://juejin.cn/post/7555077475740581940

精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝15   💬8   ❤️4
655
🥈
我愛JS
📝3   💬11   ❤️7
213
🥉
AppleLily
📝1   💬4   ❤️1
71
#4
御魂
💬1  
3
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付