转盘抽奖脚本自己撸

demo 效果

需求

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

书写伪代码

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

  • 实现一个类 class,只要传入一个元素节点,就可以控制转动,
  • 转动角度和时长以及动画曲线 可 通过参数进行配置

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

  1. 第一 需要一个 设置参数的 方法
  2. 第二需要提供一个 开启动画的方法
  3. 第三,既然是动画,脱不开定时器功能,所以需要一个动画主循环

这里再提供一个 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 下载,查看

你可能感兴趣的:(javascript,抽奖)