微信小程序中实现自定义颜色选择器(简陋版对比精致版)

来源

最开始项目中实现这个颜色选择器,ui给的就是非常设计:弹框选择颜色方块,点击确定,因为后面颜色值很单调,用户不一定知道颜色值,就建议可以使用中国色(735种色调提供选择)进行配置到面板上,然后甲方就采用了使用跳转到页面配置颜色值方式;

精致版-颜色选择器-完整实现

简陋版,弹框的方式这就不概述了,精致版的优势就是735种色调,从中国色网站上复制接口返回的所有颜色值,进行配置到色板上供用户选择或搜索

  1. 代码中创建js文件,保存为对象进行导出

  2. 代码中导入使用,优化页面效果【完整代码】

    bash 复制代码
    const colorData = require('../../utils/china-colors.js').list;
    
    Page({
      data: {
        // 分类
        colorCategories: [
          { name: "全部颜色", key: "all" },
          { name: "白色系", key: "white" },
          { name: "黄色系", key: "yellow" },
          { name: "红色系", key: "red" },
          { name: "绿色系", key: "green" },
          { name: "蓝色系", key: "blue" },
          { name: "紫色系", key: "purple" },
          { name: "棕色系", key: "brown" },
          { name: "灰色系", key: "gray" }
        ],
        activeTab: 0,
    
        // 全部/当前/渲染列表
        allColors: [],
        categorizedColors: {},
        currentColors: [],
        renderColors: [],
    
        // 懒加载核心
        pageIndex: 1,
        pageSize: 60,
        loading: false,
        noMore: false,
    
        // 选中 & 搜索
        selectedColor: null,
        searchKey: "",
    
        // 节流锁:防止快速滚动重复触发加载
        isLoading: false,
    
    		fromType: '',
      },
    
      onLoad(options) {
    		// 接收来源标记
    		this.setData({
    			fromType: options.from || 'bg'
    		})
    
        const allColors = colorData.map(item => ({
          ...item,
          hex: item.hex.toLowerCase(),
          lightness: this.getLightness(item.RGB)
        }));
    
        const categorizedColors = this.categorizeChineseColors(allColors);
        Object.keys(categorizedColors).forEach(key => {
          categorizedColors[key].sort((a, b) => a.lightness - b.lightness).reverse();
        });
    
        const initList = allColors.sort((a, b) => a.lightness - b.lightness).reverse();
    
        this.setData({
          allColors,
          categorizedColors,
          currentColors: initList,
          renderColors: initList.slice(0, 60),
          pageIndex: 1,
          noMore: initList.length <= 60
        });
      },
    
      // 颜色分类(精准不串类)
      categorizeChineseColors(colors) {
        const categories = {
          white: [], yellow: [], red: [], green: [], blue: [], purple: [], brown: [], gray: []
        };
        colors.forEach(color => {
          const name = color.name;
          const [r, g, b] = color.RGB.split(',').map(Number);
    
          if (/黄|金|橙|橘|杏|柠|葵|麦/.test(name)) {categories.yellow.push(color);return;}
          if (/红|赤|朱|丹|绯|绛|粉|樱/.test(name)) {categories.red.push(color);return;}
          if (/绿|青|翠|碧|苔|竹/.test(name)) {categories.green.push(color);return;}
          if (/蓝|靛|苍|海|天/.test(name)) {categories.blue.push(color);return;}
          if (/紫|堇|茄|丁香/.test(name)) {categories.purple.push(color);return;}
          if (/棕|褐|咖|茶|栗|赭/.test(name)) {categories.brown.push(color);return;}
          if (/灰|墨|黑|玄|乌/.test(name)) {categories.gray.push(color);return;}
          if (/白|米|雪|霜|乳|玉/.test(name)) {categories.white.push(color);return;}
    
          // HSL兜底
          const max = Math.max(r, g, b);
          const min = Math.min(r, g, b);
          const delta = max - min;
          let hue = 0;
          if(delta !== 0){
            if(max === r) hue = ((g - b) / delta) % 6;
            if(max === g) hue = (b - r) / delta + 2;
            if(max === b) hue = (r - g) / delta + 4;
          }
          hue = Math.round(hue * 60);
          if (hue < 0) hue += 360;
    
          const lightness = (max + min) / 2 / 255;
          const saturation = delta === 0 ? 0 : delta / (1 - Math.abs(2 * lightness - 1));
    
          if (lightness > 0.92 || saturation < 0.08) categories.white.push(color);
          else if (hue >= 0 && hue < 25) categories.red.push(color);
          else if (hue >= 25 && hue < 70) categories.yellow.push(color);
          else if (hue >= 70 && hue < 160) categories.green.push(color);
          else if (hue >= 160 && hue < 240) categories.blue.push(color);
          else if (hue >= 240 && hue < 310) categories.purple.push(color);
          else categories.gray.push(color);
        });
        return categories;
      },
    
      getLightness(rgbStr) {
        const [r, g, b] = rgbStr.split(',').map(Number);
        return 0.299 * r + 0.587 * g + 0.114 * b;
      },
    
      // 切换分类 重置懒加载
      switchTab(e) {
        const index = e.currentTarget.dataset.index;
        const key = this.data.colorCategories[index].key;
        let currentColors = key === "all" ? this.data.allColors : (this.data.categorizedColors[key] || []);
    
        const list = currentColors;
        const render = list.slice(0, this.data.pageSize);
    
        this.setData({
          activeTab: index,
          currentColors: list,
          renderColors: render,
          pageIndex: 1,
          loading: false,
          noMore: list.length <= this.data.pageSize,
          selectedColor: null
        });
      },
    
      // 滚动触底加载更多 + 加载中动画 + 防重复
      async loadMoreColors() {
        const { loading, noMore, isLoading, pageIndex, pageSize, currentColors, renderColors } = this.data;
        if (loading || noMore || isLoading) return;
    
        this.setData({ loading: true, isLoading: true });
    
        // 模拟流畅加载动画间隔
        await new Promise(resolve => setTimeout(resolve, 280));
    
        const start = pageIndex * pageSize;
        const end = start + pageSize;
        const newList = currentColors.slice(start, end);
    
        if (newList.length === 0) {
          this.setData({ loading: false, noMore: true, isLoading: false });
          return;
        }
    
        this.setData({
          renderColors: [...renderColors, ...newList],
          pageIndex: pageIndex + 1,
          loading: false,
          isLoading: false,
          noMore: end >= currentColors.length
        });
      },
    
      // 选中颜色
    	selectColor(e) {
    		const color = e.currentTarget.dataset.color;
    		this.setData({ selectedColor: color });
    		// 复制功能(新增)
    		let copyText = `${color.name} ${color.hex}`;
    		wx.setClipboardData({
    			data: copyText,
    			success: () => {
    				wx.showToast({
    					title: '已复制:' + color.name,
    					icon: 'none',
    					duration: 1500
    				});
    			}
    		});
    	},
    
      // 搜索
      handleSearch(e) {
        const key = e.detail.value.trim().toLowerCase();
        this.setData({ searchKey: key });
    
        if (!key) {
          this.switchTab({ currentTarget: { dataset: { index: this.data.activeTab } } });
          return;
        }
    
        const filter = this.data.allColors.filter(item =>
          item.name.includes(key) ||
          item.hex.includes(key) ||
          item.RGB.includes(key) ||
          item.CMYK.includes(key) ||
          item.pinyin.includes(key)
        );
    
        const render = filter.slice(0, this.data.pageSize);
        this.setData({
          activeTab: 0,
          currentColors: filter,
          renderColors: render,
          pageIndex: 1,
          noMore: filter.length <= this.data.pageSize,
          selectedColor: null
        });
      },
    
      // 确认选择
      // confirmColor() {
      //   if (!this.data.selectedColor) return;
      //   const pages = getCurrentPages();
      //   const prev = pages[pages.length - 2];
      //   if (prev) {
      //     prev.setData({ logoTextColor: this.data.selectedColor });
      //   }
      //   wx.showToast({ title: `已选${this.data.selectedColor.name}`, icon: 'success' });
      //   setTimeout(() => wx.navigateBack(), 1200);
      // },
    	// 确认选择(真正可用版)
    // 最终稳定版
    // 确认选择(完整版:支持6个颜色入口)
    confirmColor() {
      if (!this.data.selectedColor) return;
    
      const selectedHex = this.data.selectedColor.hex;
      const fromType = this.data.fromType;
    
      // 全局传值(最稳定,不跨页调用)
      getApp().globalData.selectedColorInfo = {
        color: selectedHex,
        from: fromType
      };
    
      wx.showToast({
        title: `已应用:${this.data.selectedColor.name}`,
        icon: 'success',
        duration: 1200
      });
    
      setTimeout(() => {
        wx.navigateBack({
          fail: err => console.log('返回失败', err)
        });
      }, 600);
    },
    
    });
    bash 复制代码
    <view class="container">
      <!-- 顶部搜索 -->
      <view class="search-box">
        <icon type="search" size="18" class="search-icon"></icon>
        <input 
          class="search-input" 
          placeholder="颜色名称或拼音 / HEX、RGB、CMYK" 
          bindinput="handleSearch"
          value="{{searchKey}}"
        />
      </view>
    
      <!-- 分类标签 自动换行 无横向滚动 -->
      <view class="tabs">
        <view 
          class="tab-item {{activeTab === index ? 'active' : ''}}" 
          wx:for="{{colorCategories}}" 
          wx:key="index"
          bindtap="switchTab"
          data-index="{{index}}"
        >
          {{item.name}}
        </view>
      </view>
    
      <!-- 颜色列表 滚动加载 -->
      <scroll-view 
        scroll-y 
        class="color-grid-container"
        bindscrolltolower="loadMoreColors"
        enhanced
        fast-decelerate
      >
        <view class="color-grid">
          <view 
            class="color-item {{selectedColor && selectedColor.id === item.id ? 'selected' : ''}}" 
            wx:for="{{renderColors}}" 
            wx:key="id"
            bindtap="selectColor"
            data-color="{{item}}"
          >
            <view class="color-swatch" style="background-color: {{item.hex}};"></view>
            <view class="color-name">{{item.name}}</view>
          </view>
        </view>
    
        <!-- 加载中 / 没有更多 -->
        <view class="load-more-box">
          <view wx:if="{{loading}}" class="loading">
            <text class="loading-text">加载中...</text>
          </view>
          <view wx:if="{{noMore && !loading}}" class="no-more">
            <text class="no-more-text">没有更多颜色了</text>
          </view>
        </view>
      </scroll-view>
    
      <!-- 底部选中栏 -->
      <view class="bottom-bar">
        <view class="selected-preview">
          <view class="preview-swatch" style="background-color: {{selectedColor ? selectedColor.hex : '#ffffff'}};"></view>
          <view class="preview-info">
            <view class="preview-name">{{selectedColor ? selectedColor.name : '请选择颜色'}}</view>
            <view class="preview-hex">{{selectedColor ? selectedColor.hex : ''}}</view>
          </view>
        </view>
        <button 
          class="confirm-btn" 
          disabled="{{!selectedColor}}"
          bindtap="confirmColor"
        >
          确认使用
        </button>
      </view>
    </view>
    bash 复制代码
    /* 全局 */
    page {
      background: #f7f8fa;
    }
    .container {
      padding: 24rpx;
      padding-bottom: 140rpx;
      box-sizing: border-box;
    }
    
    /* 搜索框 */
    .search-box {
      background: #fff;
      border-radius: 16rpx;
      padding: 16rpx;
      margin-bottom: 20rpx;
      display: flex;
      align-items: center;
      box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.04);
    }
    .search-icon {
      margin-right: 16rpx;
      color: #999;
    }
    .search-input {
      flex: 1;
      font-size: 28rpx;
      color: #333;
    }
    
    /* 标签 自动换行 不横向滚动 */
    .tabs {
      display: flex;
      flex-wrap: wrap;
      gap: 16rpx;
      margin-bottom: 24rpx;
    }
    .tab-item {
      padding: 12rpx 28rpx;
      background: #fff;
      border-radius: 30rpx;
      font-size: 26rpx;
      color: #666;
      transition: all 0.25s ease;
    }
    /* ========== 替换为你项目【主题色】 ========== */
    .tab-item.active {
      background: #f28e16;
      color: #fff;
    }
    
    /* 滚动容器 */
    .color-grid-container {
      height: calc(100vh - 300rpx);
    }
    
    /* 4列等分布局 宽松不拥挤 */
    .color-grid {
      display: grid;
      grid-template-columns: repeat(4, 1fr);
      gap: 30rpx 20rpx;
    }
    
    /* 颜色卡片 清爽留白 */
    .color-item {
      background: #fff;
      border-radius: 16rpx;
      padding: 16rpx;
      transition: all 0.2s ease;
    }
    .color-item:active {
      transform: scale(0.97);
    }
    /* 选中主题色边框 */
    .color-item.selected {
      border: 2rpx solid #f28e16;
    }
    .color-swatch {
      width: 100%;
      height: 130rpx;
      border-radius: 12rpx;
      margin-bottom: 12rpx;
    }
    .color-name {
      font-size: 24rpx;
      color: #555;
      text-align: center;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
    
    /* 加载更多区域 */
    .load-more-box {
      width: 100%;
      text-align: center;
      padding: 30rpx 0;
    }
    .loading-text {
      font-size: 24rpx;
      color: #999;
    }
    .no-more-text {
      font-size: 24rpx;
      color: #bbb;
    }
    
    /* 底部栏 */
    .bottom-bar {
      position: fixed;
      bottom: 0;
      left: 0;
      right: 0;
      background: #fff;
      padding: 24rpx 30rpx;
      box-shadow: 0 -2rpx 15rpx rgba(0,0,0,0.06);
      display: flex;
      align-items: center;
      justify-content: space-between;
      box-sizing: border-box;
    }
    .preview-swatch {
      width: 80rpx;
      height: 80rpx;
      border-radius: 12rpx;
      border: 1rpx solid #eee;
    }
    .selected-preview {
      display: flex;
      align-items: center;
      gap: 20rpx;
    }
    .preview-name {
      font-size: 28rpx;
      color: #333;
    }
    .preview-hex {
      font-size: 24rpx;
      color: #999;
      margin-top: 6rpx;
    }
    .confirm-btn {
      width: 220rpx;
      height: 76rpx;
      /* line-height: 76rpx; */
      font-size: 28rpx;
      background: #f28e16;
      color: #fff;
      border-radius: 12rpx;
      margin: 0;
    }
    .confirm-btn[disabled] {
      background: #c8d8f5;
      color: #fff;
    }
相关推荐
杰建云1672 小时前
2026年第三方平台制作微信小程序多少钱?
微信小程序·小程序·小程序制作
vipbic17 小时前
独立开发复盘:我用 Uni-app + Strapi v5 肝了一个“会上瘾”的打卡小程序
前端·微信小程序
全栈小51 天前
【小程序】微信小程序在体验版发起支付的时候提示“由于小程序违规,支付功能暂时无法使用”,是不是一脸懵逼
微信小程序·小程序
jingqingdai32 天前
微信小程序 Canvas 2D 踩坑指南:如何优雅地导出高清长图?(附 AI 辅助实录)
人工智能·微信小程序·小程序
qq_433502182 天前
微信小程序更新机制踩坑记录:updateInfo 为什么总是读到旧数据?
微信小程序·小程序·notepad++
QQ22792391023 天前
Java springboot基于微信小程序的智慧旅游导游系统景点门票酒店预订(源码+文档+运行视频+讲解视频)
java·spring boot·微信小程序·maven·vuejs
笨笨狗吞噬者4 天前
uni-app 运行时揭秘:styleIsolation 的转化
前端·微信小程序·uni-app
double_eggm4 天前
微信小程序3
微信小程序·小程序
怀君4 天前
Uniapp——微信小程序Canvas层级过高问题解决
微信小程序·小程序·uni-app