轉盤抽獎腳本本身擼

demo 效果javascript

需求

不少場景都須要作各類活動,抽獎最是司空見慣了,跑馬燈的,轉盤的,下面先花幾分鐘擼出一個轉盤的吧,固然網上至少有一打的 demo 可供參考。
真的只須要一點點時間而已。css

書寫僞代碼

實現一個東西,通常都先寫僞代碼,這裏也不例外。
初步想法功能主要有兩點:html

  • 實現一個類 class,只要傳入一個元素節點,就能夠控制轉動,
  • 轉動角度和時長以及動畫曲線 可 經過參數進行配置

考慮一下,這個類須要什麼方法功能?
根據上面的兩個需求,java

  1. 第一 須要一個 設置參數的 方法
  2. 第二須要提供一個 開啓動畫的方法
  3. 第三,既然是動畫,脫不開定時器功能,因此須要一個動畫主循環

這裏再提供一個 init 方法用做初始化操做,好比設置參數或者還有其餘處理,增長兼容性。
下面是僞代碼css3

class RotatePlate {
  constructor(options) {
    this.init();
  }
  /**
   * 初始化操做
   */
  init() {
    this.setOptions();
  }
  /**
   * 啓動轉動函數
   */
  rotate() {}
  /**
   * 設置配置參數
   */
  setOptions() {}
  /**
   * 動畫主循環
   */
  _animate() {}
}

實現參數 options 配置方法

爲了方便使用和兼容處理,咱們開發者經常使用的作法就是配置默認參數,而後用 調用者的參數去覆蓋默認參數。因此,先給類增長一些默認配置,以下:git

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 方法中進行調用,這個方法實現沒什麼難度,就是對象合併操做github

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 版本的。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 代碼,順便看看咱們的腳本代碼變成了什麼樣子:緩存

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <style>
    .ball {
      width: 100px;
      height: 100px;
      background: red;
      margin: 40px auto;
    }
  </style>
</head>
<body>
  <div class="ball" id="ball"></div>
  <script>
  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() {
      // 初始化參數
      this.setOptions(Object.assign({}, this.customOps));
      // 設置一次初始角度
      this._rotate(this._angle);
    }
    /**
    * 啓動轉動函數
    */
    rotate() {}
    /**
    * 設置配置參數
    */
    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)`;
    }
    /**
    * 動畫主循環
    */
    _animate() {}
  }

  /**
  * 判斷運行環境支持的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;
  }

  </script>
  <script>
    const r = new RotatePlate({
      el: document.querySelector('#ball'),
      angle: 60,
      animateTo: 1800,
    })
  </script>
</body>

</html>

實現動畫主循環

寫到這裏,雖說了一大推話,可是代碼去掉註釋,真的尚未幾行。
可是咱們只差一個定時器循環了,接下里實現這個主循環,不斷更新 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 下載,查看

相關文章
相關標籤/搜索