鼠标滑动切割效果

2,592 阅读5分钟

鼠标滑动切割效果

效果预览

鼠标滑动切割.gif

一、理论基础

将两张相同的图片 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>
  </body>
  <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>
</html>