Skip to content

Color Picker 取色器

概述

Color Picker 取色器是一个用于颜色选择的表单组件,提供多种颜色选择方式,包括色板选择、HSV 调节、RGB 输入等。它支持多种颜色格式,并提供了丰富的自定义选项。

学习目标

  • 掌握 Color Picker 的基本概念和使用场景
  • 学会基础颜色选择功能的实现
  • 了解不同颜色格式的支持
  • 掌握预设颜色和自定义颜色的配置
  • 学会颜色选择器的高级功能
  • 了解颜色选择器在实际项目中的应用
  • 掌握 API 的完整使用方法

基础用法

基本颜色选择器

最简单的颜色选择器:

vue
<template>
  <div>
    <h4>基本颜色选择器</h4>
    <el-color-picker v-model="color1" />
    <p>选中的颜色:{{ color1 }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const color1 = ref('#409EFF')
</script>

选择透明度

支持透明度选择的颜色选择器:

vue
<template>
  <div>
    <h4>支持透明度</h4>
    <el-color-picker v-model="color2" show-alpha />
    <p>选中的颜色:{{ color2 }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const color2 = ref('rgba(64, 158, 255, 0.8)')
</script>

预设颜色

提供预设颜色选项:

vue
<template>
  <div>
    <h4>预设颜色</h4>
    <el-color-picker
      v-model="color3"
      :predefine="predefineColors"
    />
    <p>选中的颜色:{{ color3 }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const color3 = ref('#409EFF')

const predefineColors = [
  '#ff4500',
  '#ff8c00',
  '#ffd700',
  '#90ee90',
  '#00ced1',
  '#1e90ff',
  '#c71585',
  'rgba(255, 69, 0, 0.68)',
  'rgb(255, 120, 0)',
  'hsv(51, 100, 98)',
  'hsva(120, 40, 94, 0.5)',
  'hsl(181, 100%, 37%)',
  'hsla(209, 100%, 56%, 0.73)',
  '#c7158577'
]
</script>

不同尺寸

提供不同尺寸的颜色选择器:

vue
<template>
  <div>
    <h4>不同尺寸</h4>
    <div class="size-demo">
      <el-color-picker v-model="color4" size="large" />
      <el-color-picker v-model="color4" />
      <el-color-picker v-model="color4" size="small" />
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const color4 = ref('#409EFF')
</script>

<style scoped>
.size-demo {
  display: flex;
  align-items: center;
  gap: 16px;
}
</style>

高级功能

颜色格式

支持多种颜色格式:

vue
<template>
  <div>
    <h4>不同颜色格式</h4>
    
    <div class="format-demo">
      <div class="format-item">
        <label>HEX 格式:</label>
        <el-color-picker v-model="hexColor" color-format="hex" />
        <span>{{ hexColor }}</span>
      </div>
      
      <div class="format-item">
        <label>RGB 格式:</label>
        <el-color-picker v-model="rgbColor" color-format="rgb" />
        <span>{{ rgbColor }}</span>
      </div>
      
      <div class="format-item">
        <label>HSL 格式:</label>
        <el-color-picker v-model="hslColor" color-format="hsl" />
        <span>{{ hslColor }}</span>
      </div>
      
      <div class="format-item">
        <label>HSV 格式:</label>
        <el-color-picker v-model="hsvColor" color-format="hsv" />
        <span>{{ hsvColor }}</span>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const hexColor = ref('#409EFF')
const rgbColor = ref('rgb(64, 158, 255)')
const hslColor = ref('hsl(209, 100%, 63%)')
const hsvColor = ref('hsv(209, 75%, 100%)')
</script>

<style scoped>
.format-demo {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.format-item {
  display: flex;
  align-items: center;
  gap: 12px;
}

.format-item label {
  width: 100px;
  font-weight: 500;
}

.format-item span {
  min-width: 200px;
  padding: 4px 8px;
  background-color: #f5f7fa;
  border-radius: 4px;
  font-family: monospace;
  font-size: 14px;
}
</style>

禁用状态

vue
<template>
  <div>
    <h4>禁用状态</h4>
    <el-color-picker v-model="color5" disabled />
    <p>禁用的颜色选择器</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const color5 = ref('#409EFF')
</script>

实际应用示例

主题配置器

创建一个主题颜色配置组件:

vue
<template>
  <div class="theme-configurator">
    <h3>主题配置器</h3>
    
    <div class="config-section">
      <h4>主要颜色</h4>
      <div class="color-configs">
        <div class="color-config">
          <label>主色调:</label>
          <el-color-picker
            v-model="themeColors.primary"
            :predefine="primaryPresets"
            @change="updateTheme"
          />
          <span class="color-value">{{ themeColors.primary }}</span>
        </div>
        
        <div class="color-config">
          <label>成功色:</label>
          <el-color-picker
            v-model="themeColors.success"
            :predefine="successPresets"
            @change="updateTheme"
          />
          <span class="color-value">{{ themeColors.success }}</span>
        </div>
        
        <div class="color-config">
          <label>警告色:</label>
          <el-color-picker
            v-model="themeColors.warning"
            :predefine="warningPresets"
            @change="updateTheme"
          />
          <span class="color-value">{{ themeColors.warning }}</span>
        </div>
        
        <div class="color-config">
          <label>危险色:</label>
          <el-color-picker
            v-model="themeColors.danger"
            :predefine="dangerPresets"
            @change="updateTheme"
          />
          <span class="color-value">{{ themeColors.danger }}</span>
        </div>
      </div>
    </div>
    
    <div class="config-section">
      <h4>背景颜色</h4>
      <div class="color-configs">
        <div class="color-config">
          <label>页面背景:</label>
          <el-color-picker
            v-model="themeColors.background"
            show-alpha
            @change="updateTheme"
          />
          <span class="color-value">{{ themeColors.background }}</span>
        </div>
        
        <div class="color-config">
          <label>卡片背景:</label>
          <el-color-picker
            v-model="themeColors.cardBackground"
            show-alpha
            @change="updateTheme"
          />
          <span class="color-value">{{ themeColors.cardBackground }}</span>
        </div>
      </div>
    </div>
    
    <div class="preview-section">
      <h4>预览效果</h4>
      <div class="theme-preview" :style="previewStyles">
        <div class="preview-card">
          <h5>示例卡片</h5>
          <p>这是一个示例文本内容。</p>
          <div class="preview-buttons">
            <button class="btn btn-primary">主要按钮</button>
            <button class="btn btn-success">成功按钮</button>
            <button class="btn btn-warning">警告按钮</button>
            <button class="btn btn-danger">危险按钮</button>
          </div>
        </div>
      </div>
    </div>
    
    <div class="actions">
      <el-button @click="resetTheme">重置主题</el-button>
      <el-button type="primary" @click="exportTheme">导出配置</el-button>
      <el-button type="success" @click="applyTheme">应用主题</el-button>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus'

const themeColors = ref({
  primary: '#409EFF',
  success: '#67C23A',
  warning: '#E6A23C',
  danger: '#F56C6C',
  background: '#ffffff',
  cardBackground: '#f5f7fa'
})

const primaryPresets = [
  '#409EFF', '#1890ff', '#722ed1', '#eb2f96', '#52c41a'
]

const successPresets = [
  '#67C23A', '#52c41a', '#73d13d', '#95de64', '#b7eb8f'
]

const warningPresets = [
  '#E6A23C', '#fa8c16', '#ffa940', '#ffc53d', '#ffd666'
]

const dangerPresets = [
  '#F56C6C', '#ff4d4f', '#ff7875', '#ffa39e', '#ffccc7'
]

const previewStyles = computed(() => ({
  backgroundColor: themeColors.value.background,
  '--primary-color': themeColors.value.primary,
  '--success-color': themeColors.value.success,
  '--warning-color': themeColors.value.warning,
  '--danger-color': themeColors.value.danger,
  '--card-background': themeColors.value.cardBackground
}))

const updateTheme = () => {
  ElMessage.success('主题已更新')
}

const resetTheme = () => {
  themeColors.value = {
    primary: '#409EFF',
    success: '#67C23A',
    warning: '#E6A23C',
    danger: '#F56C6C',
    background: '#ffffff',
    cardBackground: '#f5f7fa'
  }
  ElMessage.info('主题已重置')
}

const exportTheme = () => {
  const config = JSON.stringify(themeColors.value, null, 2)
  navigator.clipboard.writeText(config)
  ElMessage.success('主题配置已复制到剪贴板')
}

const applyTheme = () => {
  // 这里可以实现应用主题的逻辑
  ElMessage.success('主题已应用')
}
</script>

<style scoped>
.theme-configurator {
  max-width: 800px;
  padding: 20px;
  border: 1px solid #dcdfe6;
  border-radius: 8px;
}

.config-section {
  margin-bottom: 24px;
}

.config-section h4 {
  margin: 0 0 16px 0;
  color: #303133;
  border-bottom: 1px solid #e4e7ed;
  padding-bottom: 8px;
}

.color-configs {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.color-config {
  display: flex;
  align-items: center;
  gap: 12px;
}

.color-config label {
  width: 100px;
  font-weight: 500;
  color: #606266;
}

.color-value {
  min-width: 120px;
  padding: 4px 8px;
  background-color: #f5f7fa;
  border-radius: 4px;
  font-family: monospace;
  font-size: 12px;
  color: #909399;
}

.preview-section {
  margin-bottom: 24px;
}

.theme-preview {
  padding: 20px;
  border-radius: 8px;
  border: 1px solid #e4e7ed;
}

.preview-card {
  background-color: var(--card-background);
  padding: 20px;
  border-radius: 6px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.preview-card h5 {
  margin: 0 0 12px 0;
  color: #303133;
}

.preview-card p {
  margin: 0 0 16px 0;
  color: #606266;
  line-height: 1.5;
}

.preview-buttons {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.btn {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  transition: all 0.3s;
}

.btn-primary {
  background-color: var(--primary-color);
  color: white;
}

.btn-success {
  background-color: var(--success-color);
  color: white;
}

.btn-warning {
  background-color: var(--warning-color);
  color: white;
}

.btn-danger {
  background-color: var(--danger-color);
  color: white;
}

.btn:hover {
  opacity: 0.8;
  transform: translateY(-1px);
}

.actions {
  display: flex;
  gap: 12px;
  justify-content: flex-end;
  padding-top: 16px;
  border-top: 1px solid #e4e7ed;
}
</style>

图表颜色配置

创建一个图表颜色配置组件:

vue
<template>
  <div class="chart-color-config">
    <h3>图表颜色配置</h3>
    
    <div class="config-panel">
      <div class="color-series">
        <h4>数据系列颜色</h4>
        <div class="series-colors">
          <div
            v-for="(color, index) in chartColors"
            :key="index"
            class="color-item"
          >
            <span class="color-label">系列 {{ index + 1 }}:</span>
            <el-color-picker
              v-model="chartColors[index]"
              @change="updateChart"
            />
            <button
              v-if="chartColors.length > 1"
              @click="removeColor(index)"
              class="remove-btn"
            >
              ×
            </button>
          </div>
        </div>
        
        <div class="color-actions">
          <el-button @click="addColor" size="small">添加颜色</el-button>
          <el-button @click="randomColors" size="small" type="primary">随机颜色</el-button>
        </div>
      </div>
      
      <div class="background-config">
        <h4>背景配置</h4>
        <div class="bg-colors">
          <div class="color-item">
            <span class="color-label">图表背景:</span>
            <el-color-picker
              v-model="chartBackground"
              show-alpha
              @change="updateChart"
            />
          </div>
          
          <div class="color-item">
            <span class="color-label">网格颜色:</span>
            <el-color-picker
              v-model="gridColor"
              show-alpha
              @change="updateChart"
            />
          </div>
        </div>
      </div>
    </div>
    
    <div class="chart-preview">
      <h4>预览效果</h4>
      <div class="mock-chart" :style="chartStyles">
        <div class="chart-title">示例图表</div>
        <div class="chart-content">
          <div class="chart-bars">
            <div
              v-for="(color, index) in chartColors.slice(0, 5)"
              :key="index"
              class="chart-bar"
              :style="{
                backgroundColor: color,
                height: `${Math.random() * 80 + 20}%`
              }"
            >
              <span class="bar-label">{{ index + 1 }}</span>
            </div>
          </div>
          <div class="chart-legend">
            <div
              v-for="(color, index) in chartColors.slice(0, 5)"
              :key="index"
              class="legend-item"
            >
              <span class="legend-color" :style="{ backgroundColor: color }"></span>
              <span class="legend-text">数据系列 {{ index + 1 }}</span>
            </div>
          </div>
        </div>
      </div>
    </div>
    
    <div class="export-section">
      <h4>导出配置</h4>
      <div class="export-options">
        <el-button @click="exportAsJSON">导出 JSON</el-button>
        <el-button @click="exportAsCSS">导出 CSS</el-button>
        <el-button @click="exportAsArray">导出数组</el-button>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus'

const chartColors = ref([
  '#409EFF',
  '#67C23A',
  '#E6A23C',
  '#F56C6C',
  '#909399'
])

const chartBackground = ref('#ffffff')
const gridColor = ref('rgba(0, 0, 0, 0.1)')

const chartStyles = computed(() => ({
  backgroundColor: chartBackground.value,
  '--grid-color': gridColor.value
}))

const addColor = () => {
  const randomColor = `#${Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0')}`
  chartColors.value.push(randomColor)
  updateChart()
}

const removeColor = (index) => {
  if (chartColors.value.length > 1) {
    chartColors.value.splice(index, 1)
    updateChart()
  }
}

const randomColors = () => {
  chartColors.value = chartColors.value.map(() => 
    `#${Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0')}`
  )
  updateChart()
}

const updateChart = () => {
  ElMessage.success('图表已更新')
}

const exportAsJSON = () => {
  const config = {
    colors: chartColors.value,
    background: chartBackground.value,
    grid: gridColor.value
  }
  const json = JSON.stringify(config, null, 2)
  navigator.clipboard.writeText(json)
  ElMessage.success('JSON 配置已复制到剪贴板')
}

const exportAsCSS = () => {
  let css = ':root {\n'
  chartColors.value.forEach((color, index) => {
    css += `  --chart-color-${index + 1}: ${color};\n`
  })
  css += `  --chart-background: ${chartBackground.value};\n`
  css += `  --chart-grid: ${gridColor.value};\n`
  css += '}'
  
  navigator.clipboard.writeText(css)
  ElMessage.success('CSS 变量已复制到剪贴板')
}

const exportAsArray = () => {
  const array = `const chartColors = ${JSON.stringify(chartColors.value, null, 2)}`
  navigator.clipboard.writeText(array)
  ElMessage.success('颜色数组已复制到剪贴板')
}
</script>

<style scoped>
.chart-color-config {
  max-width: 800px;
  padding: 20px;
  border: 1px solid #dcdfe6;
  border-radius: 8px;
}

.config-panel {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
  margin-bottom: 24px;
}

.color-series h4,
.background-config h4 {
  margin: 0 0 16px 0;
  color: #303133;
  border-bottom: 1px solid #e4e7ed;
  padding-bottom: 8px;
}

.series-colors,
.bg-colors {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.color-item {
  display: flex;
  align-items: center;
  gap: 12px;
}

.color-label {
  width: 100px;
  font-weight: 500;
  color: #606266;
  font-size: 14px;
}

.remove-btn {
  width: 24px;
  height: 24px;
  border: none;
  background-color: #f56c6c;
  color: white;
  border-radius: 50%;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  line-height: 1;
}

.remove-btn:hover {
  background-color: #f78989;
}

.color-actions {
  margin-top: 16px;
  display: flex;
  gap: 8px;
}

.chart-preview {
  margin-bottom: 24px;
}

.chart-preview h4 {
  margin: 0 0 16px 0;
  color: #303133;
}

.mock-chart {
  border: 1px solid var(--grid-color);
  border-radius: 8px;
  padding: 20px;
  min-height: 300px;
}

.chart-title {
  text-align: center;
  font-size: 18px;
  font-weight: 500;
  color: #303133;
  margin-bottom: 20px;
}

.chart-content {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.chart-bars {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  height: 200px;
  padding: 0 20px;
  border-bottom: 2px solid var(--grid-color);
  border-left: 2px solid var(--grid-color);
}

.chart-bar {
  flex: 1;
  min-height: 20px;
  border-radius: 4px 4px 0 0;
  position: relative;
  transition: all 0.3s;
}

.chart-bar:hover {
  opacity: 0.8;
  transform: translateY(-2px);
}

.bar-label {
  position: absolute;
  bottom: -20px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 12px;
  color: #909399;
}

.chart-legend {
  display: flex;
  justify-content: center;
  gap: 20px;
  flex-wrap: wrap;
}

.legend-item {
  display: flex;
  align-items: center;
  gap: 6px;
}

.legend-color {
  width: 12px;
  height: 12px;
  border-radius: 2px;
}

.legend-text {
  font-size: 12px;
  color: #606266;
}

.export-section h4 {
  margin: 0 0 16px 0;
  color: #303133;
}

.export-options {
  display: flex;
  gap: 12px;
}

@media (max-width: 768px) {
  .config-panel {
    grid-template-columns: 1fr;
  }
  
  .chart-bars {
    padding: 0 10px;
  }
  
  .chart-legend {
    gap: 12px;
  }
}
</style>

API 文档

ColorPicker Attributes

名称说明类型默认值
model-value / v-model绑定值string
disabled是否禁用booleanfalse
size尺寸enum
show-alpha是否支持透明度选择booleanfalse
color-format写入 v-model 的颜色的格式enumhex
popper-classColorPicker 下拉框的类名string
predefine预定义颜色array
validate-event输入时是否触发表单的校验booleantrue
tabindexColorPicker 的 tabindexstring / number0
labelColorPicker 的 aria-labelstring
idColorPicker 的 idstring

ColorPicker Events

名称说明类型
change当绑定值变化时触发Function
active-change面板中当前显示的颜色发生改变时触发Function

ColorPicker Methods

名称说明类型
focus使 ColorPicker 获取焦点Function
blur使 ColorPicker 失去焦点Function

实践练习

练习1:品牌色彩管理器

创建一个品牌色彩管理系统:

vue
<template>
  <div class="brand-color-manager">
    <h3>品牌色彩管理器</h3>
    
    <div class="brand-colors">
      <div
        v-for="(brand, index) in brandColors"
        :key="index"
        class="brand-item"
      >
        <div class="brand-header">
          <input
            v-model="brand.name"
            class="brand-name"
            placeholder="品牌名称"
          />
          <button @click="removeBrand(index)" class="remove-brand">删除</button>
        </div>
        
        <div class="color-palette">
          <div
            v-for="(color, colorIndex) in brand.colors"
            :key="colorIndex"
            class="color-slot"
          >
            <el-color-picker
              v-model="brand.colors[colorIndex]"
              :predefine="commonColors"
            />
            <button
              @click="removeColor(index, colorIndex)"
              class="remove-color"
            >
              ×
            </button>
          </div>
          
          <button @click="addColor(index)" class="add-color">+</button>
        </div>
      </div>
    </div>
    
    <div class="actions">
      <el-button @click="addBrand">添加品牌</el-button>
      <el-button type="primary" @click="exportBrands">导出配置</el-button>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'

const brandColors = ref([
  {
    name: '主品牌',
    colors: ['#409EFF', '#67C23A', '#E6A23C']
  }
])

const commonColors = [
  '#ff4500', '#ff8c00', '#ffd700', '#90ee90', '#00ced1',
  '#1e90ff', '#c71585', '#409EFF', '#67C23A', '#E6A23C'
]

const addBrand = () => {
  brandColors.value.push({
    name: `品牌 ${brandColors.value.length + 1}`,
    colors: ['#409EFF']
  })
}

const removeBrand = (index) => {
  brandColors.value.splice(index, 1)
}

const addColor = (brandIndex) => {
  brandColors.value[brandIndex].colors.push('#409EFF')
}

const removeColor = (brandIndex, colorIndex) => {
  if (brandColors.value[brandIndex].colors.length > 1) {
    brandColors.value[brandIndex].colors.splice(colorIndex, 1)
  }
}

const exportBrands = () => {
  const config = JSON.stringify(brandColors.value, null, 2)
  navigator.clipboard.writeText(config)
  ElMessage.success('品牌配置已复制到剪贴板')
}
</script>

<style scoped>
.brand-color-manager {
  max-width: 800px;
  padding: 20px;
}

.brand-colors {
  display: flex;
  flex-direction: column;
  gap: 20px;
  margin-bottom: 20px;
}

.brand-item {
  border: 1px solid #dcdfe6;
  border-radius: 8px;
  padding: 16px;
}

.brand-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}

.brand-name {
  border: none;
  font-size: 16px;
  font-weight: 500;
  color: #303133;
  background: transparent;
  outline: none;
}

.brand-name:focus {
  border-bottom: 1px solid #409EFF;
}

.remove-brand {
  padding: 4px 8px;
  border: 1px solid #f56c6c;
  background: white;
  color: #f56c6c;
  border-radius: 4px;
  cursor: pointer;
}

.color-palette {
  display: flex;
  gap: 12px;
  align-items: center;
  flex-wrap: wrap;
}

.color-slot {
  position: relative;
  display: flex;
  align-items: center;
}

.remove-color {
  position: absolute;
  top: -8px;
  right: -8px;
  width: 16px;
  height: 16px;
  border: none;
  background: #f56c6c;
  color: white;
  border-radius: 50%;
  cursor: pointer;
  font-size: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.add-color {
  width: 32px;
  height: 32px;
  border: 2px dashed #dcdfe6;
  background: transparent;
  border-radius: 4px;
  cursor: pointer;
  color: #909399;
  font-size: 18px;
}

.add-color:hover {
  border-color: #409EFF;
  color: #409EFF;
}

.actions {
  display: flex;
  gap: 12px;
}
</style>

练习2:渐变色生成器

创建一个渐变色生成器:

vue
<template>
  <div class="gradient-generator">
    <h3>渐变色生成器</h3>
    
    <div class="gradient-config">
      <div class="color-stops">
        <h4>颜色节点</h4>
        <div class="stops-list">
          <div
            v-for="(stop, index) in colorStops"
            :key="index"
            class="color-stop"
          >
            <el-color-picker v-model="stop.color" @change="updateGradient" />
            <input
              v-model.number="stop.position"
              type="range"
              min="0"
              max="100"
              @input="updateGradient"
              class="position-slider"
            />
            <span class="position-value">{{ stop.position }}%</span>
            <button
              v-if="colorStops.length > 2"
              @click="removeStop(index)"
              class="remove-stop"
            >
              ×
            </button>
          </div>
        </div>
        
        <el-button @click="addStop" size="small">添加节点</el-button>
      </div>
      
      <div class="gradient-direction">
        <h4>渐变方向</h4>
        <div class="direction-options">
          <label v-for="dir in directions" :key="dir.value">
            <input
              v-model="direction"
              type="radio"
              :value="dir.value"
              @change="updateGradient"
            />
            {{ dir.label }}
          </label>
        </div>
      </div>
    </div>
    
    <div class="gradient-preview">
      <h4>预览效果</h4>
      <div class="preview-box" :style="{ background: gradientCSS }">
        <div class="preview-content">
          <h5>渐变预览</h5>
          <p>这是渐变背景效果</p>
        </div>
      </div>
    </div>
    
    <div class="gradient-output">
      <h4>CSS 代码</h4>
      <div class="code-output">
        <pre>{{ gradientCSS }}</pre>
        <el-button @click="copyCSS" size="small" type="primary">复制代码</el-button>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus'

const colorStops = ref([
  { color: '#409EFF', position: 0 },
  { color: '#67C23A', position: 100 }
])

const direction = ref('to right')

const directions = [
  { label: '向右', value: 'to right' },
  { label: '向左', value: 'to left' },
  { label: '向下', value: 'to bottom' },
  { label: '向上', value: 'to top' },
  { label: '右下角', value: 'to bottom right' },
  { label: '左下角', value: 'to bottom left' },
  { label: '右上角', value: 'to top right' },
  { label: '左上角', value: 'to top left' }
]

const gradientCSS = computed(() => {
  const stops = colorStops.value
    .sort((a, b) => a.position - b.position)
    .map(stop => `${stop.color} ${stop.position}%`)
    .join(', ')
  
  return `linear-gradient(${direction.value}, ${stops})`
})

const addStop = () => {
  const newPosition = Math.floor(Math.random() * 100)
  colorStops.value.push({
    color: '#409EFF',
    position: newPosition
  })
  updateGradient()
}

const removeStop = (index) => {
  colorStops.value.splice(index, 1)
  updateGradient()
}

const updateGradient = () => {
  // 触发重新计算
}

const copyCSS = () => {
  navigator.clipboard.writeText(`background: ${gradientCSS.value};`)
  ElMessage.success('CSS 代码已复制到剪贴板')
}
</script>

<style scoped>
.gradient-generator {
  max-width: 800px;
  padding: 20px;
}

.gradient-config {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: 24px;
  margin-bottom: 24px;
}

.color-stops h4,
.gradient-direction h4 {
  margin: 0 0 16px 0;
  color: #303133;
}

.stops-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: 16px;
}

.color-stop {
  display: flex;
  align-items: center;
  gap: 12px;
}

.position-slider {
  flex: 1;
  height: 6px;
  border-radius: 3px;
  background: #e4e7ed;
  outline: none;
  cursor: pointer;
}

.position-value {
  width: 40px;
  text-align: center;
  font-size: 12px;
  color: #909399;
}

.remove-stop {
  width: 20px;
  height: 20px;
  border: none;
  background: #f56c6c;
  color: white;
  border-radius: 50%;
  cursor: pointer;
  font-size: 14px;
}

.direction-options {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.direction-options label {
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  font-size: 14px;
}

.preview-box {
  height: 200px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 24px;
}

.preview-content {
  text-align: center;
  color: white;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

.preview-content h5 {
  margin: 0 0 8px 0;
  font-size: 18px;
}

.preview-content p {
  margin: 0;
  opacity: 0.9;
}

.code-output {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 16px;
  background: #f5f7fa;
  border-radius: 6px;
}

.code-output pre {
  flex: 1;
  margin: 0;
  font-family: 'Courier New', monospace;
  font-size: 14px;
  color: #303133;
  word-break: break-all;
  white-space: pre-wrap;
}
</style>

常见问题

1. 颜色格式转换

问题:需要在不同颜色格式之间转换

解决方案

javascript
// 颜色格式转换工具函数
const colorUtils = {
  // HEX 转 RGB
  hexToRgb(hex) {
    const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
    return result ? {
      r: parseInt(result[1], 16),
      g: parseInt(result[2], 16),
      b: parseInt(result[3], 16)
    } : null
  },
  
  // RGB 转 HEX
  rgbToHex(r, g, b) {
    return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`
  },
  
  // HSL 转 RGB
  hslToRgb(h, s, l) {
    h /= 360
    s /= 100
    l /= 100
    
    const hue2rgb = (p, q, t) => {
      if (t < 0) t += 1
      if (t > 1) t -= 1
      if (t < 1/6) return p + (q - p) * 6 * t
      if (t < 1/2) return q
      if (t < 2/3) return p + (q - p) * (2/3 - t) * 6
      return p
    }
    
    const q = l < 0.5 ? l * (1 + s) : l + s - l * s
    const p = 2 * l - q
    
    return {
      r: Math.round(hue2rgb(p, q, h + 1/3) * 255),
      g: Math.round(hue2rgb(p, q, h) * 255),
      b: Math.round(hue2rgb(p, q, h - 1/3) * 255)
    }
  }
}

2. 颜色对比度检查

问题:需要检查颜色对比度是否符合无障碍标准

解决方案

javascript
const checkContrast = (color1, color2) => {
  // 计算相对亮度
  const getLuminance = (color) => {
    const rgb = colorUtils.hexToRgb(color)
    const [r, g, b] = [rgb.r, rgb.g, rgb.b].map(c => {
      c = c / 255
      return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
    })
    return 0.2126 * r + 0.7152 * g + 0.0722 * b
  }
  
  const lum1 = getLuminance(color1)
  const lum2 = getLuminance(color2)
  const brightest = Math.max(lum1, lum2)
  const darkest = Math.min(lum1, lum2)
  
  return (brightest + 0.05) / (darkest + 0.05)
}

// 使用示例
const contrastRatio = checkContrast('#ffffff', '#000000')
if (contrastRatio >= 4.5) {
  console.log('符合 WCAG AA 标准')
} else if (contrastRatio >= 3) {
  console.log('符合 WCAG AA 大文本标准')
} else {
  console.log('不符合无障碍标准')
}

3. 颜色主题生成

问题:根据主色生成完整的颜色主题

解决方案

javascript
const generateTheme = (primaryColor) => {
  const rgb = colorUtils.hexToRgb(primaryColor)
  
  // 生成不同深浅的颜色
  const generateShades = (baseRgb, steps = 9) => {
    const shades = []
    for (let i = 0; i < steps; i++) {
      const factor = (i + 1) / (steps + 1)
      const r = Math.round(baseRgb.r + (255 - baseRgb.r) * (1 - factor))
      const g = Math.round(baseRgb.g + (255 - baseRgb.g) * (1 - factor))
      const b = Math.round(baseRgb.b + (255 - baseRgb.b) * (1 - factor))
      shades.push(colorUtils.rgbToHex(r, g, b))
    }
    return shades
  }
  
  return {
    primary: primaryColor,
    shades: generateShades(rgb),
    success: '#67C23A',
    warning: '#E6A23C',
    danger: '#F56C6C',
    info: '#909399'
  }
}

最佳实践

  1. 提供预设颜色:为常用场景提供预设颜色选项
  2. 支持透明度:在需要时启用透明度选择
  3. 颜色格式统一:在项目中保持颜色格式的一致性
  4. 无障碍考虑:确保颜色对比度符合无障碍标准
  5. 性能优化:避免频繁的颜色变化导致的重渲染
  6. 用户体验:提供颜色预览和实时反馈

总结

Color Picker 取色器是一个功能丰富的颜色选择组件,支持:

  • 多种颜色格式(HEX、RGB、HSL、HSV)
  • 透明度选择
  • 预设颜色配置
  • 不同尺寸和状态
  • 丰富的事件和方法

掌握 Color Picker 组件的使用,能够为用户提供直观、便捷的颜色选择体验,广泛应用于主题配置、图表设计、UI 定制等场景。

参考资料

Element Plus Study Guide