资讯专栏INFORMATION COLUMN

转盘抽奖脚本自己撸

赵春朋 / 2631人阅读

摘要:效果需求很多场景都需要做各种活动,抽奖最是司空见惯了,跑马灯的,转盘的,下面先花几分钟撸出一个转盘的吧,当然网上至少有一打的可供参考。但是我们只差一个定时器循环了接下里实现这个主循环,不断更新值就可以了。

demo 效果

需求

很多场景都需要做各种活动,抽奖最是司空见惯了,跑马灯的,转盘的,下面先花几分钟撸出一个转盘的吧,当然网上至少有一打的 demo 可供参考。
真的只需要一点点时间而已。

书写伪代码

实现一个东西,一般都先写伪代码,这里也不例外。
初步想法功能主要有两点:

实现一个类 class,只要传入一个元素节点,就可以控制转动,

转动角度和时长以及动画曲线 可 通过参数进行配置

考虑一下,这个类需要什么方法功能?
根据上面的两个需求,

第一 需要一个 设置参数的 方法

第二需要提供一个 开启动画的方法

第三,既然是动画,脱不开定时器功能,所以需要一个动画主循环

这里再提供一个 init 方法用作初始化操作,比如设置参数或者还有其他处理,增加兼容性。
下面是伪代码

class RotatePlate {
  constructor(options) {
    this.init();
  }
  /**
   * 初始化操作
   */
  init() {
    this.setOptions();
  }
  /**
   * 启动转动函数
   */
  rotate() {}
  /**
   * 设置配置参数
   */
  setOptions() {}
  /**
   * 动画主循环
   */
  _animate() {}
}
实现参数 options 配置方法

为了方便使用和兼容处理,我们开发者常用的做法就是配置默认参数,然后用 调用者的参数去覆盖默认参数。所以,先给类增加一些默认配置,如下:

constructor(options) {
    // 缓存用户数据参数,稍后会进行默认参数覆盖,之后再做重复初始化也会很方便
    this.customOps = options;
    // 默认参数配置
    this._parameters = {
      angle: 0, // 元素初始角度设置
      animateTo: 0, // 目标角度
      step: null, // 旋转过程中 回调函数
      easing: function(t, b, c, d) {
        return -c * ((t = t / d - 1) * t * t * t - 1) + b;
      }, // 缓动曲线,关于动画 缓动函数不太懂得可以自行搜索
      duration: 3000, // 动画旋转时间
      callback: () => {}, // 旋转完成回调
    };
    this._angle = 0; // 维护一个当前时刻角度的私有变量,下面setOptions就知道如何使用了
  }
  this.init(); // 调用初始化方法

接下来实现 setOptions 方法,并且在 init 方法中进行调用,这个方法实现没什么难度,就是对象合并操作

init() {
  // 初始化参数
  this.setOptions(Object.assign({}, this.customOps));
}

/**
  * 设置配置参数
  */
setOptions(parameters) {
  try {
    // 获取容器元素
    if (typeof parameters.el === "string") {
      this.el = document.querySelector(parameters.el);
    } else {
      this.el = parameters.el;
    }
    // 获取初始角度
    if (typeof parameters.angle === "number") this._angle = parameters.angle;
    // 合并参数
    Object.assign(this._parameters, parameters);
  } catch (err) {}
}
实现一个设置元素样式的方法

上面设置完了参数,我们还没办法验证参数是否正确。
为了实现旋转效果,我们有两种方式可供选择,第一种,利用 css3 的 transform,第二种利用 canvas 绘图。其实两种方法都比较简单,这里先选择 css3 实现一版,结尾再附上 canvas 版本的。

// 实现一个css3样式,我们需要处理兼容性,确定浏览器类型,选择对应的属性
// 这里添加一个辅助方法
/**
 * 判断运行环境支持的css,用作css制作动画
 */
function getSupportCSS() {
  let supportedCSS = null;
  const styles = document.getElementsByTagName("head")[0].style;
  const toCheck = "transformProperty WebkitTransform OTransform msTransform MozTransform".split(
    " "
  );
  for (var a = 0; a < toCheck.length; a++) {
    if (styles[toCheck[a]] !== undefined) {
      supportedCSS = toCheck[a];
      break;
    }
  }
  return supportedCSS;
}
// 在constructor构造函数里面增加一个属性
this.supportedCSS = getSupportCSS();

然后 给类增加一个 设置样式的方法_rotate

_rotate(angle) {
    const el = this.el;
    this._angle = angle; // 更新当前角度
    el.style[this.supportedCSS] = `rotate3d(0,0,1,${angle % 360}deg)`;
}
// 在 init里面增加 _rotate方法,初始化元素 初始角度
init() {
  // 初始化参数
  this.setOptions(Object.assign({}, this.customOps));
  // 设置一次初始角度
  this._rotate(this._angle);
}

在这里,就可以写一个 demo,进行测试了,当然还么有动画,只能测试初始角度 angle 设置
demo 代码,顺便看看我们的脚本代码变成了什么样子:




  
  
  
  Document
  


  
实现动画主循环

写到这里,虽然说了一大推话,但是代码去掉注释,真的还没有几行。
但是我们只差一个定时器循环了,接下里实现这个主循环,不断更新 angle 值就可以了。
说起定时器,我们需要计算动画时间来 判断是否应该取消定时器等等,一些附加操作,所以增加一个_animateStart 方法清理和计时, 下面直接上代码,

  _animateStart() {
    if (this._timer) {
      clearTimeout(this._timer);
    }
    this._animateStartTime = Date.now();
    this._animateStartAngle = this._angle;
    this._animate();
  }
  /**
   * 动画主循环
   */
  _animate() {
    const actualTime = Date.now();
    const checkEnd =
      actualTime - this._animateStartTime > this._parameters.duration;
      // 判断是否应该 结束
    if (checkEnd) {
      clearTimeout(this._timer);
    } else {
      if (this.el) {
        // 调用缓动函数,获取当前时刻 angle值
        var angle = this._parameters.easing(
          actualTime - this._animateStartTime,
          this._animateStartAngle,
          this._parameters.animateTo - this._animateStartAngle,
          this._parameters.duration
        );
        // 设置 el 元素的样式
        this._rotate(~~(angle * 10) / 10);
      }
      if (this._parameters.step) {
        this._parameters.step.call(this, this._angle);
      }
      // 循环调用
      this._timer = setTimeout(() => {
        this._animate();
      }, 10);
    }
    // 完成回调
    if (this._parameters.callback && checkEnd) {
      this._angle = this._parameters.animateTo;
      this._rotate(this._angle);
      this._parameters.callback.call(this);
    }
  }

然后再 rotate 方法调用_animateStart 就好了

  rotate() {
    if (this._angle === this._parameters.animateTo) {
      this._rotate(this._angle);
    } else {
      this._animateStart();
    }
  }

至此,一个利用 css3 实现的脚本就完成了,有木有很简单,下面贴上完整代码.

/**
 * 功能: 开发一个旋转插件,传入一个元素节点即可控制旋转
 * 转动角度和时长以及动画曲线 可 通过参数进行配置
 *
 * 参数列表:
 *  - dom 需要一个容器 必选
 *  - angle 初始角度   非必选
 *  - animateTo 结束角度  非必选
 *  - duration 动画时长  非必选
 *  - easing 缓动函数  非必选
 *  - step 角度每次更新调用  非必选
 *  - callback 动画结束回调  非必选
 */
class RotatePlate {
  constructor(options) {
    // 获取当前运行环境支持的样式属性
    this.supportedCSS = getSupportCSS();
    // 缓存用户数据
    this.customOps = options;
    // 私有参数
    this._parameters = {
      angle: 0,
      animateTo: 0,
      step: null,
      easing: function(t, b, c, d) {
        return -c * ((t = t / d - 1) * t * t * t - 1) + b;
      },
      duration: 3000,
      callback: () => {},
    };
    this._angle = 0; // 当前时刻角度
    this.init();
  }
  /**
   * 初始化操作
   */
  init(newOps = {}) {
    // 初始化参数
    this.setOptions(Object.assign({}, this.customOps, newOps));
    // 设置一次初始角度
    this._rotate(this._angle);
  }
  /**
   * 启动转动函数
   */
  rotate() {
    if (this._angle === this._parameters.animateTo) {
      this._rotate(this._angle);
    } else {
      this._animateStart();
    }
  }
  /**
   * 设置配置参数
   */
  setOptions(parameters) {
    try {
      // 获取容器元素
      if (typeof parameters.el === "string") {
        this.el = document.querySelector(parameters.el);
      } else {
        this.el = parameters.el;
      }
      // 获取初始角度
      if (typeof parameters.angle === "number") this._angle = parameters.angle;
      // 合并参数
      Object.assign(this._parameters, parameters);
    } catch (err) {}
  }
  _rotate(angle) {
    const el = this.el;
    this._angle = angle; // 更新当前角度
    el.style[this.supportedCSS] = `rotate3d(0,0,1,${angle % 360}deg)`;
  }
  _animateStart() {
    if (this._timer) {
      clearTimeout(this._timer);
    }
    this._animateStartTime = Date.now();
    this._animateStartAngle = this._angle;
    this._animate();
  }
  /**
   * 动画主循环
   */
  _animate() {
    const actualTime = Date.now();
    const checkEnd =
      actualTime - this._animateStartTime > this._parameters.duration;
    if (checkEnd) {
      clearTimeout(this._timer);
    } else {
      if (this.el) {
        var angle = this._parameters.easing(
          actualTime - this._animateStartTime,
          this._animateStartAngle,
          this._parameters.animateTo - this._animateStartAngle,
          this._parameters.duration
        );
        this._rotate(~~(angle * 10) / 10);
      }
      if (this._parameters.step) {
        this._parameters.step.call(this, this._angle);
      }
      this._timer = setTimeout(() => {
        this._animate();
      }, 10);
    }
    if (this._parameters.callback && checkEnd) {
      this._angle = this._parameters.animateTo;
      this._rotate(this._angle);
      this._parameters.callback.call(this);
    }
  }
}

/**
 * 判断运行环境支持的css,用作css制作动画
 */
function getSupportCSS() {
  let supportedCSS = null;
  const styles = document.getElementsByTagName("head")[0].style;
  const toCheck = "transformProperty WebkitTransform OTransform msTransform MozTransform".split(
    " "
  );
  for (var a = 0; a < toCheck.length; a++) {
    if (styles[toCheck[a]] !== undefined) {
      supportedCSS = toCheck[a];
      break;
    }
  }
  return supportedCSS;
}

下面再补充一个 canvas 实现的动画方法:

  _rotateCanvas(angle) {
    // devicePixelRatio 是设备像素比,为了解决canvas模糊问题设置的
    // 原理把 canvas画布扩大,然后缩小显示在屏幕
    this._angle = angle;
    const radian = ((angle % 360) * Math.PI) / 180;
    this._canvas.width = this.WIDTH * this.devicePixelRatio;
    this._canvas.height = this.HEIGHT * this.devicePixelRatio;
    // 解决模糊问题
    this._cnv.scale(this.devicePixelRatio, this.devicePixelRatio);
    // 平移canvas原点
    this._cnv.translate(this.WIDTH / 2, this.HEIGHT / 2);
    // 平移后旋转
    this._cnv.rotate(radian);
    // 移回 原点
    this._cnv.translate(-this.WIDTH / 2, -this.HEIGHT / 2);
    this._cnv.drawImage(this._img, 0, 0, this.WIDTH, this.HEIGHT);
  }
源码下载

完整源码请到 github 下载,查看

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/96919.html

相关文章

  • Vue+CSS3实现转盘抽奖

    摘要:最近有个转盘抽奖的需求,搜了一下现有的轮子,有的是用的动画函数实现的,有的是用绘图然后再用高频率的调用旋转方法,前者太老了没法简单移植到项目,后者感觉性能表现可能不会太好。核心思路是用以及实现旋转动画,使用和绘制出定位较为精确的轮盘奖项。 最近有个转盘抽奖的需求,搜了一下现有的轮子,有的是用jQuery的动画函数实现的,有的是用canvas绘图然后再用高频率的setTimeout调用旋...

    mj 评论0 收藏0
  • 转盘抽奖-- 自己

    摘要:自己很菜,不可否认。所以上周日试试水,看看自己能否写个圆盘抽奖的。效果图代码外部圆内部园请输入外数字开始基础旋转的圆以自己的宽度的一半为,以父盒子的高度一半为,作为旋转点。 自己很菜,不可否认。所以上周日试试水,看看自己能否写个圆盘抽奖的demo。// github L6zt开发思路 布局 css rotate 布局; 抽奖过渡效果,采用css3 transition; 动态计算抽奖...

    gaara 评论0 收藏0
  • canvas之转盘抽奖

    摘要:最近工作中重构了抽奖转盘,给大家提供一个开发转盘抽奖的思路需求转盘根据奖品数量不同而有变化目录结构由于业务需要所以开发了两个版本抽奖,和,不过部分只能替换图片,没有功能逻辑。 最近工作中重构了抽奖转盘,给大家提供一个开发转盘抽奖的思路 需求 1、转盘根据奖品数量不同而有变化 2、canvas 目录结构 showImg(https://segmentfault.com/img/bVbwL...

    _ang 评论0 收藏0
  • 小程序制作大转盘抽奖功能,巨简约的代码!!

    摘要:公司说要做个活动,迎接双十一。。大概的思路就是页面有个转盘,然后转盘是一个背景。转盘的指针也是用图片。如下图然后第二步,翻查小程序文档。最后根据小程序文档说,这个参数需要输出。为真的时候运行正常旋转的方法,为假的时候。 公司说要做个活动,迎接双十一。。然后最怕的事情出现了,就是做转盘。以前没怎么写过动画,特别怕这些东西。。。好了,废话不说。直入正题。 首先,先构图。大概的思路就是页面...

    TesterHome 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<