去年我使用react编写了chrome控制台风格的取色器,现在使用vue重写chrome风格取色器,基本实现了相同的功能。 但由于vue.js与react.js的区别,具体实现上也有了较大区别。 Chrome风格取色器

data设置所需的基本数据

由于取色器是由2个input[type=range]和取色圈div.color-circle的位置的数据来驱动的,于是从这三个数据入手,data属性如下:

  data: function () {
      return {
        rgbRangeValue: 765, // rgb滑块值
        alphaRangeValue: 255, // alpha滑块值
        imgData: [], // 保存canvas imageDate
        width: 296, // 宽度
        measures: ['hex', 'rgba', 'hsla'], // 可选的颜色值单位
        measure: 'hex', // 所选的颜色值单位
        circleIsShow: false, // 显示取色环
        x: 256, // 取色环的x坐标
        y: 0 // 取色环的y坐标
      }
    },

rgb滑块的设定是以red、magenta、blue、cyan、lime、yellow这六个主色调之间平滑渐变来设置数值的。 如下图所示,将input[type=range]的step设置为1,每个主色调占据255个数值, 那么总共有255*6 =765个数值,每滑动255个数的距离就改变一个主色调。 input设置max rgb滑块决定的是矩形色块右上角的颜色值,x和y则共同决定了在矩形色块内点选的颜色, 右上角的rgb颜色值为[255,255,255],左下角和右下角都为[0,0,0],真个矩形内颜色平滑过渡。 这样就可以在滑动rgb滑块时,所有属于当前色调的颜色都能显示在矩形色块内。 同时,当我们在最下面的输入框内输入一个任意的颜色值,可以在矩形色块内找到这个颜色值,其步骤如下:

在计算属性里,将leftTop设置为取色圈的css样式对象并绑定到模版中去, 当点击事件、拖拽事件改变x,y坐标时,同步改变取色圈的位置。

rgba为rgb颜色滑块和alpha滑块所决定的取色矩形右上角的颜色值。 alpha、pickRgb、pickHsl、hex分别为选取不同颜色单位时,显示或输入的颜色值。

mounted方法

在生命周期mounted钩子方法中,发现立即执行renderColorBlock()方法,并不能渲染到取色块, 必须等设置setTimeout来延迟执行。可能是mounted生命周期内, canvas的getContextcreateImageData未执行的原因。

mounted: function () {
      this.imgData = getImageData(this.width, ...this.rgba)
      const that = this
      setTimeout(function () {
        that.renderColorBlock()
      }, 20)
    },

watch方法

在watch方法内,监听rgba计算属性,当rgba改变时,立即重新渲染取色矩形canvas。 监听的属性不能过多,最初还监听了x,y的属性改变时,随之改变pickRgb的值,从而导致数据混乱。

watch: {
      rgba: function () {
        this.imgData = getImageData(this.width, ...this.rgba)
        this.renderColorBlock()
        console.log(this.rgba)
      }
    },

其他代码

完整项目代码详见我的github项目3air

methods: {
      renderColorBlock: function () {
        const dom = this.$refs['colorBlock']
        const context = dom.getContext('2d')
        const image = context.createImageData(this.width, this.width / 2)
//        console.log(this.imgData)
        for (let i = 0; i < image.data.length; i++) {
          image.data[i] = this.imgData[i]
        }
        context.putImageData(image, 0, 0)
      },
      nextMeasure: function () {
        const index = Array.prototype.indexOf.call(this.measures, this.measure)
        var measure = this.measure
        index === 0 ? measure = this.measures[this.measures.length - 1] : measure = this.measures[index - 1]
        this.measure = measure
      },
      lastMeasure: function () {
        const index = Array.prototype.indexOf.call(this.measures, this.measure)
        var measure = ''
        index === this.measures.length - 1 ? measure = this.measures[0] : measure = this.measures[index + 1]
        this.measure = measure
      },
      positionToRgb: function () {
        var pickRgb = []
        pickRgb[0] = parseInt((255 * (1 - this.x / 256) + this.rgba[0] * this.x / 256) * (1 - this.y / 128))
        pickRgb[1] = parseInt((255 * (1 - this.x / 256) + this.rgba[1] * this.x / 256) * (1 - this.y / 128))
        pickRgb[2] = parseInt((255 * (1 - this.x / 256) + this.rgba[2] * this.x / 256) * (1 - this.y / 128))
        console.log(this.x, this.y)
        console.log(pickRgb)
        this.pickRgb = pickRgb
      },
      showColorCircle: function (event) {
        if (this.circleIsShow !== true) {
          this.circleIsShow = true
        }
        const rect = this.$refs['colorBlock'].getBoundingClientRect()
        const top = rect.top
        const left = rect.left
        const disX = event.clientX - left
        const disY = event.clientY - top
//        console.log(top, left)
        this.x = disX
        this.y = disY
        this.positionToRgb()
//        console.log(disX, disY)
      },
      handleMouseDown: function (event) {
        if (this.circleIsShow !== true) {
          this.circleIsShow = true
        }
        const target = event.target
        const disX = event.clientX - target.offsetLeft  // 鼠标与元素边框的距离
        const disY = event.clientY - target.offsetTop
        const that = this
        document.onmousemove = function (event) {  // 鼠标按下拖动
          var l = event.clientX - disX
          var t = event.clientY - disY
          l > 250 ? l = 250 : false
          l < -5 ? l = -5 : false
          t > 254 / 2 - 5 ? t = 254 / 2 - 5 : false
          t < -5 ? t = -5 : false
          that.x = l + 5
          that.y = t + 5
          that.positionToRgb()
        }
        document.onmouseup = function (event) {
          document.onmousemove = null
          document.onmouseup = null
        }
      },
      changeValue: function (event) {
        console.log(event.target.value)
      },
      changeRgb: function () {
        this.pickRgb.forEach((cv, index, arr) => {
          arr[index] = parseInt(cv)
        })
        const max = Math.max.apply(null, this.pickRgb)
        const min = Math.min.apply(null, this.pickRgb)
        if (max > 255 || min < 0) {
          return console.log('the value is out of range')
        }
        let maxIndex = this.pickRgb.indexOf(max)
        let minIndex = this.pickRgb.indexOf(min)
        if (max === min) {
          maxIndex = 0
          minIndex = 1
        }
        console.log(maxIndex, minIndex)
        let rgb = []
        let index = `${maxIndex},${minIndex}`
        switch (index) {
          case '0,1':
            rgb = [255, 0, this.pickRgb[2] * max / 255]
            this.rgbRangeValue = parseInt(rgb[2])
            break
          case '2,1':
            rgb = [this.pickRgb[0] * max / 255, 0, 255]
            this.rgbRangeValue = parseInt(2 * 255 - rgb[0])
            break
          case '2,0':
            rgb = [0, this.pickRgb[1] * max / 255, 255]
            this.rgbRangeValue = parseInt(2 * 255 + rgb[1])
            break
          case '1,0':
            rgb = [0, 255, this.pickRgb[2] * max / 255]
            this.rgbRangeValue = parseInt(4 * 255 - rgb[2])
            break
          case '1,2':
            rgb = [this.pickRgb[0] * max / 255, 255, 0]
            this.rgbRangeValue = parseInt(4 * 255 + rgb[0])
            break
          case '0,2':
            rgb = [255, this.pickRgb[1] * max / 255, 0]
            this.rgbRangeValue = parseInt(6 * 255 - rgb[1])
            break
        }
        this.rgba = rgb.concat([this.rgba[3]])
        console.log(max, min)
        this.x = parseInt(255 - 255 * min / max)
        this.y = parseInt(128 - 128 * max / 255)
        this.circleIsShow = true
        console.log(this.rgbRangeValue)
        console.log(this.rgba)
      },
      changeHex: function (event) {
        console.log(event.target.getAttribute('value'))
        const hex = this.hex
        console.log(this.hex)
        const pattern1 = /^#[0-9a-fA-F]{3}$/
        const pattern2 = /^#[0-9a-fA-F]{6}$/
        if (pattern1.test(hex)) {
          this.pickRgb[0] = parseInt(hex.substr(1, 1), 16) * 17
          this.pickRgb[1] = parseInt(hex.substr(2, 1), 16) * 17
          this.pickRgb[2] = parseInt(hex.substr(3, 1), 16) * 17
        } else if (pattern2.test(hex)) {
          this.pickRgb[0] = parseInt(hex.substr(1, 2), 16)
          this.pickRgb[1] = parseInt(hex.substr(3, 2), 16)
          this.pickRgb[2] = parseInt(hex.substr(5, 2), 16)
        } else {
          return console.log('the input value is out of range')
        }
        this.changeRgb()
      },
      changeHsl: function () {
        let rgb = hslToRgb(parseInt(this.pickHsl[0]) / 360,
          parseInt(this.pickHsl[1].replace('%', '')) / 100,
          parseInt(this.pickHsl[2].replace('%', '')) / 100)
        console.log(rgb)
        this.pickRgb = rgb
        this.changeRgb()
      },
      changeAlpha: function (event) {
        console.log(event.target.value)
        let alpha = parseInt((parseFloat(event.target.value) * 255))
        this.alphaRangeValue = alpha
        this.rgba[3] = alpha
        console.log(this.rgba)
      }
    }