douchaoyang / blog

blogs use issues
0 stars 0 forks source link

举一反三,从抽奖计步器中抽象出一个方法 runtowalk,解决此类问题 #10

Open douchaoyang opened 1 year ago

douchaoyang commented 1 year ago

在日常开发中常碰到抽奖类的需求,如“九宫格”,“翻牌”等以循环计步最后减速落在某一位置的形式。

image

此类问题可形象的模拟为一段跑步,加速-匀速-减速,这里简化为匀速-减速模式。

曲线方程大致为:

image

我的设想为,横坐标代表步数,纵坐标代表从当前落脚点到下一个落脚点所花的时间(ms)。 初始时间设为100ms,终点设为1100ms(方便计算),拐点从距离终点5步开始。 设常量总步数为 k。 所得方程为y = 40 * (x - (k - 5)) ** 2

function 参数有 totalstartstoploopcb total 是代表跑完一圈需要多少步,比如九宫格去掉中间的按钮就是8start 是开始的落脚点,设置这个参数是因为多次抽奖需从上一次抽奖的落脚点开始。 stop 是结束的落脚点。 loop 是需要循环的次数。 cb 是一个回调函数,参数为每次的计步点,比如 total8,参数每次返回就是 1 2 3 4 5 6 7 8 1 2 3...

实现为:

function runtowalk({ total, start, stop, loop, cb }) {
  return new Promise((resolve, reject) => {
    var step = total - start + total * loop + stop;
    var foot = 1;

    function y(x, k) {
      return 40 * (x - k + 5) ** 2;
    }

    // 运行匿名函数,接收 callee
    (function () {
      cb(((foot - 1 + start - 1 + total) % total) + 1);
      var time = foot > step - 5 ? y(foot, step) : 100;
      if (foot <= step) {
        foot++;
        setTimeout(arguments.callee, time);
      } else {
        resolve(((foot - 1 + start - 1 + total) % total) + 1);
      }
    })();
  });
}

其中 step 为所需要的总步数,function y 是减速函数。

例(html):

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>抽奖计步器</title>
    <style>
      table {
        width: 600px;
        height: 600px;
      }
      td {
        width: 30%;
        text-align: center;
      }
      .active {
        background-color: #ccc;
      }
    </style>
    <script src="https://file.eefcdn.com/campaign/eefocus/js/jquery.min.js"></script>
  </head>
  <body>
    <table border="1">
      <tr>
        <td class="step step1">1</td>
        <td class="step step2">2</td>
        <td class="step step3">3</td>
      </tr>
      <tr>
        <td class="step step8">8</td>
        <td id="start">开始</td>
        <td class="step step4">4</td>
      </tr>
      <tr>
        <td class="step step7">7</td>
        <td class="step step6">6</td>
        <td class="step step5">5</td>
      </tr>
    </table>

    <script>
      function runtowalk({ total, start, stop, loop, cb }) {
        return new Promise((resolve, reject) => {
          var step = total - start + total * loop + stop;
          var foot = 1;

          function y(x, k) {
            return 40 * (x - k + 5) ** 2;
          }

          // 运行匿名函数,接收 callee
          (function () {
            cb(((foot - 1 + start - 1 + total) % total) + 1);
            var time = foot > step - 5 ? y(foot, step) : 100;
            if (foot <= step) {
              foot++;
              setTimeout(arguments.callee, time);
            } else {
              resolve(((foot - 1 + start - 1 + total) % total) + 1);
            }
          })();
        });
      }

      var start = 1;

      $("#start").on("click", function () {
        var stop = Math.ceil(Math.random() * 8);
        console.log("stop", stop);
        runtowalk({
          total: 8,
          start: start,
          stop: stop,
          loop: 2,
          cb(a) {
            console.log(a);
            $(".step").removeClass("active");
            $(".step" + a).addClass("active");
          },
        }).then((end) => {
          start = end;
        });
      });
    </script>
  </body>
</html>