读万卷书行万里路

canvas圆形进度盘

发布时间:2018/9/4 10:25:2
    全文约1.6k
    预计需要9分钟

近期有个项目涉及到圆形进度盘,因为只有圆形的进度盘,切数量少,所以用库的话实在是大材小用,索性就自己封装了一个。

录屏有点卡屏V_V

源代码:

/**
 * 圆环统计和圆形填充统计类
 * @type {String}
 */
class circleProgress {
  /**
   * 构造函数
   * @param  {string|object|null} canvas          [canvas对象/选择器/null]
   * @param  {String} [type='circle'] [类型:circle|circleFill]
   * @param  {object} options         [配置]
   * @return {[type]}                 [description]
   */
  constructor (canvas, type = 'circle', options) {
    this.data = {}
    this.canvas = canvas ? (typeof canvas == 'string' ? document.querySelector(canvas) : canvas) : document.createElement('canvas')
    this.ctx = this.canvas.getContext('2d')
    this.ctx.imageSmoothingEnabled  = true
    this.ctx.scale(this.data.dpi, this.data.dpi)
    this.requestAnimationFrame()
    this.init(type, canvas, options)
  }
  init (type, canvas, options) {
    let defaultConfig = {
      dpi: 2,
      w: 300,
      h: 300,
      animation: false, // 标识动画
      background: "#2c0d61", // 背景
      circle: { // 圆环配置
        gradientColor: ["#05a6da", "#0f6cd9"], // 渐变色系
        lineWidth: 20, // 宽度
        lineColor: "#dedede", // 线条底色
        r: 100, // 半径
        percentage: 30, // 百分比
        margin: 10, // 内外圆间隔
        fill: true, // 是否内圆
      },
      circleFill: {
        gradientColor: ["#c8efd4", "#6bd089", "#3bd368"],
        lineWidth: 1,
        r: 100,
        percentage: 50
      },
      percentText: {
        fillStyle: "#fff",
        font: "Arial",
        fontWeight: "bold",
        fontSize: 40,
        textAlign: "center"
      }
    }
    let circleConfig = Object.assign({}, defaultConfig.circle, options.circle)
    let circleFillConfig = Object.assign({}, defaultConfig.circleFill, options.circleFill)
    let percentTextConfig = Object.assign({}, defaultConfig.percentText, options.percentText)

    this.data = Object.assign({}, defaultConfig, options)
    this.data.circle = circleConfig
    this.data.circleFill = circleFillConfig
    this.data.percentText = percentTextConfig

    this.canvas.width = this.data.w * this.data.dpi
    this.canvas.height = this.data.h * this.data.dpi

    this.data.x = this.canvas.width / 2
    this.data.y = this.canvas.height / 2

    if (!canvas) {
      this.drawBackground()
    }
    let text = ''
    if (type == 'circle') {
      text = this.data.circle.percentage
      // 计算弧度
      let endProgress = Math.PI / 180 * (360 * (this.data.circle.percentage / 100))
      let startProgress = - Math.PI / 180 * 90

      let rand = endProgress + startProgress
      if (this.animation) {
        this.drawCircal(startProgress, rand)
      } else {
        this.doAnimation('drawCircal', startProgress, rand, this.data.circle.percentage)
      }
    } else {
      text = this.data.circleFill.percentage
      let diameter = this.data.circleFill.r * this.data.dpi * 2
      let marginTop = (this.canvas.height - diameter) / 2
      let percentHeight = diameter * ( (100 - text) / 100)
      let startY = marginTop + percentHeight
      let height = this.canvas.height - marginTop
      if (this.animation) {
        this.drawCircalFill(startY, height)
      } else {
        this.doAnimation('drawCircalFill', height, startY, text)
      }
    }
    if (this.animation) {
      this.drawText(text  + "%")
    } else {
      this.doAnimation('drawText', 0, text, 0)
    }

  }

  /**
   * 画圆环
   * @param  {number} startProgress [开始弧度]
   * @param  {number} rand          [结束弧度]
   * @return {[type]}               [description]
   */
  drawCircal (startProgress, rand) {

    //绘制底图
    this.ctx.strokeStyle = this.data.circle.lineColor
    this.ctx.lineWidth = this.data.circle.lineWidth * this.data.dpi
    this.ctx.lineCap = "round"

    this.ctx.beginPath()
    this.ctx.arc(this.data.x, this.data.y, this.data.circle.r * this.data.dpi, 0, 2 * Math.PI, true)
    this.ctx.closePath()
    this.ctx.stroke()

    // 绘制进度条
    this.ctx.beginPath();
    let gradientPoint = this.linearLocation(this.data.y, this.data.circle.r, this.data.circle.lineWidth)
    let gradient = this.ctx.createLinearGradient(0, gradientPoint.start, 0, gradientPoint.end)

    let colorLen = this.data.circle.gradientColor.length
    let average = 1 / colorLen

    for (let i = 0; i < colorLen; i ++) &#123;
      gradient.addColorStop(i * average, this.data.circle.gradientColor[i])
    &#125;
    this.ctx.strokeStyle = gradient
    this.ctx.arc(this.data.x, this.data.y, this.data.circle.r * this.data.dpi, startProgress, rand, false)
    this.ctx.stroke()

    // 内部填充圆
    if (this.data.circle.fill === true) &#123;
      this.ctx.fillStyle = gradient
      this.ctx.beginPath()
      this.ctx.arc(this.data.x, this.data.y, (this.data.circle.r - this.data.circle.margin - this.data.circle.lineWidth) * this.data.dpi, 0, 2 * Math.PI, true)
      this.ctx.closePath()
      this.ctx.fill()
    &#125;

  &#125;

  /**
   * 画填充圆
   * @param  &#123;number&#125; startY [开始高度]
   * @param  &#123;number&#125; height [总高度]
   * @return &#123;[type]&#125;        [description]
   */
  drawCircalFill(startY, height) &#123;
    let midR = this.data.circleFill.r * this.data.dpi
    this.ctx.lineWidth = 1  * this.data.dpi
    this.ctx.strokeStyle = '#fff'
    this.ctx.beginPath()
    this.ctx.arc(this.data.x, this.data.y, midR, 0, 2 * Math.PI, true)
    this.ctx.closePath()
    this.ctx.stroke()

    this.ctx.clip()

    let midGradientPoint = this.linearLocation(this.data.y, this.data.circleFill.r, 1)
    var midGradient = this.ctx.createLinearGradient(0, midGradientPoint.start, 0, midGradientPoint.end);

    let colorLen = this.data.circleFill.gradientColor.length
    let average = 1 / colorLen

    for (let i = 0; i < colorLen; i ++) &#123;
      midGradient.addColorStop(i * average, this.data.circleFill.gradientColor[i])
    &#125;

    this.ctx.fillStyle = midGradient

    this.ctx.beginPath()
    this.ctx.rect(0, startY, this.canvas.width, height)
    this.ctx.closePath()
    this.ctx.fill()
  &#125;

  /**
   * 动画处理函数
   * @param  &#123;string&#125; handelName [执行动画的函数]
   * @param  &#123;number&#125; start      [开始值]
   * @param  &#123;number&#125; end        [结束值]
   * @param  &#123;number&#125; percent    [百分比]
   * @return &#123;[type]&#125;            [description]
   */
  doAnimation (handelName, start, end, percent) &#123;

    let animationEndTargit = end
    let animationCurrent = start
    let that = this
    handelName = "animation" + handelName.substring(0,1).toUpperCase()+handelName.substring(1)

    window.requestAnimationFrame(eval(handelName))

    /**
     * 圆环动画
     * @param  &#123;[type]&#125; time [description]
     * @return &#123;[type]&#125;      [description]
     */
    function animationDrawCircal (time) &#123;
      that.ctx.clearRect(0, 0 , that.canvas.width, that.canvas.height)
      that.drawBackground(false)
      animationCurrent += Math.PI / 180 * ((percent / 100 * 360) / percent)
      that.drawCircal(start, animationCurrent)
      if (animationCurrent < animationEndTargit ) &#123;
        window.requestAnimationFrame(animationDrawCircal)
      &#125;
    &#125;

    /**
     * 文字动画
     * @return &#123;[type]&#125; [description]
     */
    function animationDrawText () &#123;
      // that.ctx.clearRect(0, 0 , that.canvas.width, that.canvas.height)
      animationCurrent += 1
      that.drawText(animationCurrent + "%")
      if (animationCurrent < animationEndTargit ) &#123;
        window.requestAnimationFrame(animationDrawText)
      &#125;
    &#125;

    /**
     * 填充动画
     * @return &#123;[type]&#125; [description]
     */
    function animationDrawCircalFill () &#123;
      that.ctx.clearRect(0, 0 , that.canvas.width, that.canvas.height)
      that.drawBackground(false)
      animationCurrent -= (start - end) / percent
      that.drawCircalFill(animationCurrent, end)
      if (animationCurrent > animationEndTargit ) &#123;
        window.requestAnimationFrame(animationDrawCircalFill)
      &#125;
    &#125;
  &#125;

  /**
   * 画圆形填充
   * @return &#123;[type]&#125; [description]
   */
  drawBackground (append = true) &#123;
    this.ctx.fillStyle = this.data.background
    this.ctx.rect(0, 0, this.canvas.width, this.canvas.height)
    this.ctx.fill()

    append && document.querySelector('body').appendChild(this.canvas)
  &#125;

  /**
   * 绘制文字
   * @param  &#123;[type]&#125; context [canvas context]
   * @param  &#123;[type]&#125; text    [text]
   * @param  &#123;[type]&#125; options [配置]
   * @return &#123;[type]&#125;         [description]
   */
  drawText (text) &#123;
    for (let k in this.data.percentText) &#123;
      if (k != 'fontSize') &#123;
        this.ctx[k] = this.data.percentText[k]
      &#125;
    &#125;
    this.ctx.font = this.data.percentText['fontWeight'] + ' ' +this.data.percentText['fontSize'] * this.data.dpi + 'px' + ' ' + this.data.percentText['font']

    this.ctx.fillText(text, this.data.x, this.data.y + 10 * this.data.dpi)
  &#125;

  /**
   * 创建gradient开始和结束的点
   * @param  &#123;[type]&#125; y         [description]
   * @param  &#123;[type]&#125; r         [description]
   * @param  &#123;[type]&#125; lineWidth [description]
   * @return &#123;[type]&#125;           [description]
   */
  linearLocation(y, r, lineWidth)&#123;
      // 设定渐变背影的起始结束点
      let start = y - ((r-15)*2 + lineWidth)/2;
      let end = start + (r-15)*2 + lineWidth
      return &#123;start: start, end: end&#125;
  &#125;

  /**
   * requestAnimationFrame profill
   * @return &#123;[type]&#125; [description]
   */
  requestAnimationFrame () &#123;
    var lastTime = 0;
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) &#123;
        window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
    &#125;
    if (!window.requestAnimationFrame) window.requestAnimationFrame = function(callback, element) &#123;
        var currTime = new Date().getTime();
        var timeToCall = Math.max(0, 16 - (currTime - lastTime));
        var id = window.setTimeout(function() &#123;
            callback(currTime + timeToCall);
        &#125;, timeToCall);
        lastTime = currTime + timeToCall;
        return id;
    &#125;;
    if (!window.cancelAnimationFrame) window.cancelAnimationFrame = function(id) &#123;
        clearTimeout(id);
    &#125;;
  &#125;
&#125;

module.exports = circleProgress;

使用方法:

// 圆环有内圆
new circalProgress(null, 'circle', &#123;animation: true&#125;)

// 圆环无内圆
new circalProgress(null, 'circle', &#123;animation: true, circle: &#123;fill: false&#125;&#125;)

// 填充百分比
new circalProgress(null, 'circleFill',&#123;animation: true&#125;)
目录
文章作者:CavinHuang
文章链接:http://blog.zukmb.cn/posts/e79c6210/
版权声明:转载请注明来自CavinHuangのBlog
canvas文字粒子特效(含运动)
二叉搜索树的后序遍历序列