利用Flexbox order属性实现滑动拼图游戏

作者:半夏

时间:2026-10-29

浏览:372

本文介绍如何利用CSS Flexbox的order属性简化滑动拼图游戏的开发。通过动态调整canvas元素的order值,实现图片块的随机打乱与位置交换,无需手动计算坐标。文章提供了核心代码逻辑及移动端触摸事件处理方案,适合前端开发者快速实现此类交互效果。

文章目录

滑动拼图游戏原理

滑动拼图游戏的核心机制是将一张完整图片分割为若干等份,打乱这些分块的原始排列顺序,用户需通过滑动操作将它们重新拼合为原图。

滑动拼图游戏原理

基于Flexbox order的实现方案

开发此类游戏通常涉及随机打乱算法及元素位置交换逻辑。然而,借助Flexbox布局,浏览器可自动处理元素的排列顺序,开发者只需关注(Flexbox介绍链接)。本示例主要利用Flexbox的order属性来控制各个Flex项目的显示顺序。具体实现中,使用九个canvas元素将图片切分为九宫格,也可采用背景定位等其他方案替代。


<div class="wrap">
  <canvas></canvas>
  <canvas></canvas>
  <canvas></canvas>
  <canvas></canvas>
  <canvas></canvas>
  <canvas></canvas>
  <canvas></canvas>
  <canvas></canvas>
  <canvas></canvas>
</div>

代码实现与位置交换

该方案不仅限于九宫格,若需生成十六宫格或其他规格,相关HTML元素可动态创建。以下是生成并打乱九张图片顺序的核心代码实现。


var drawImage = function (url) {
  return new Promise(function (resolve, reject) {
    var img = new Image();
    img.onload = function () {
      resolve(img);
    };
    img.src = url;
  })
};

drawImage("2.jpg").then(function (img) {
  var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
  var random = arr.sort(function() {return Math.random() > 0.5});
  [].forEach.call(document.querySelectorAll("canvas"), function (item, i) {
    item.width = $(".wrap").clientWidth / 3;
    item.height = $(".wrap").clientHeight / 3;
    item.style.order = random[i];
    var ctx = item.getContext("2d");
    ctx.drawImage(img, img.width * (i % 3) / 3, img.height * Math.floor(i / 3) / 3, img.width / 3, img.height / 3, 0, 0, item.width, item.height);
  });
});

上述代码的关键部分在于对数组进行随机排序,并将结果赋值给每个canvas元素的order属性。浏览器会根据order值自动重排元素位置,从而完成打乱效果。


item.style.order = random[i];

代码的其他细节此处略过,重点说明如何交换两张图片的位置。这一过程出乎意料地简洁:只需交换两个canvas元素对应的order属性值即可实现位置互换。


var order1 = item.style.order;
var order2 = target.style.order;

具体操作仅需交换双方的order属性值,无需手动计算坐标或移动DOM节点。

完整代码与测试建议

以下是完整的实现代码。


<!DOCTYPE html>
<html>
<head lang="en">
  <meta charset="UTF-8">
  <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport" />
  <meta content="yes" name="apple-mobile-web-app-capable" />
  <meta content="black" name="apple-mobile-web-app-status-bar-style" />
  <meta content="telephone=no" name="format-detection" />
  <title></title>
  <style>
    html, body {
      height: 100%;
    }
    body {
      margin: 0;
      padding: 0;
      overflow: hidden;
    }
    .wrap {
      display: flex;
      flex-wrap: wrap;
      width: 100%;
      height: 100%;
      overflow: hidden;
    }
    .wrap canvas {
      width: 33.3333%;
      height: 33.3333%;
      border: 1px solid red;
      box-sizing: border-box;
    }
  </style>
</head>
<body>
<div class="wrap">
  <canvas data-value="1"></canvas>
  <canvas data-value="2"></canvas>
  <canvas data-value="3"></canvas>
  <canvas data-value="4"></canvas>
  <canvas data-value="5"></canvas>
  <canvas data-value="6"></canvas>
  <canvas data-value="7"></canvas>
  <canvas data-value="8"></canvas>
  <canvas data-value="9"></canvas>
</div>
<script>
  var $ = function (el) {
    return document.querySelector(el);
  };
  var touchMove, touchEnd;
  var drawImage = function (url) {
    return new Promise(function (resolve, reject) {
      var img = new Image();
      img.onload = function () {
        resolve(img);
      };
      img.src = url;
    })
  };
  drawImage("2.jpg").then(function (img) {
    var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    var random = arr.sort(function() {return Math.random() > 0.5});
    [].forEach.call(document.querySelectorAll("canvas"), function (item, i) {
      item.width = $(".wrap").clientWidth / 3;
      item.height = $(".wrap").clientHeight / 3;
      item.style.order = random[i];
      var ctx = item.getContext("2d");
      ctx.drawImage(img, img.width * (i % 3) / 3, img.height * Math.floor(i / 3) / 3, img.width / 3, img.height / 3, 0, 0, item.width, item.height);
    });
  });
  document.addEventListener("touchstart", function (e) {
    var target = e.target;
    if (e.target.tagName.toLowerCase() !== "canvas") {
      return;
    }
    var ctx = target.getContext("2d");
    var image = ctx.getImageData(0, 0, target.width, target.height);
    var obj = target.cloneNode(true);
    obj.getContext("2d").putImageData(image, 0, 0);
    var top = target.getBoundingClientRect().top, left = target.getBoundingClientRect().left;
    obj.style.cssText = "position: absolute; top: " + top + "px; left: " + left + "px";
    document.body.appendChild(obj);
    var point = {"x": e.touches[0].pageX, "y": e.touches[0].pageY};
    document.addEventListener("touchmove", touchMove = function (e) {
      obj.style.cssText = "position: absolute; top:" + (e.touches[0].pageY - point.y + top) + "px; left: " + (e.touches[0].pageX - point.x + left) + "px";
    });
    document.addEventListener("touchend", touchEnd = function (e) {
      var pos = {"x": e.changedTouches[0].pageX, "y": e.changedTouches[0].pageY};
      [].forEach.call(document.querySelectorAll(".wrap canvas"), function (item, i) {
        var offset = item.getBoundingClientRect();
        if (pos.x > offset.left && pos.x < (offset.left + item.width) && pos.y > offset.top && pos.y < (offset.top + item.height)) {
          var order1 = item.style.order;
          var order2 = target.style.order;
          if (obj.parentNode) {
            document.body.removeChild(obj);
          }
          item.style.order = order2;
          target.style.order = order1;
        }
      });
      document.removeEventListener("touchmove", touchMove);
      document.removeEventListener("touchend", touchEnd);
    })
  })
</script>
</body>
</html>

测试时建议使用谷歌浏览器模拟器或真实手机设备,因为该实现仅支持移动端的触摸事件。

当前代码仅实现了基础功能,尚未包含完整的游戏逻辑(如胜利判定等)。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章