前言
因为项目上需要,并且该组件目前还没见到社区有其他童鞋写过,所以想着自己造一下轮子,帮OpenHarmony建立生态出一份微薄之力。组件其实基本功能已经实现,但是并非最理想的,后续会进行维护更新,继续完善更多功能。
开发环境说明
- 工具版本:OpenHarmony DevEco Studio 3.0 Beta3
- SDK版本:3.0.0.901(API Version 8 Beta3)
- 组要组件:组件名称yg-slider
展示效果
属性
属性名 | 类型 | 默认值 | 作用 |
g-color | Array | [] | 渐变色值组,eg: #ff9800 |
step | Number | 20 | 步长,g-color属性里每两渐变色之间步长,步长越大 越精准,渲染压力越大 |
show-card | Boolean | false | 是否显示滑块上方的颜色卡片 |
组件事件
属性名 | 类型 | 返回值 | 备注 |
get-result | Function | {num:String, rgbColor:String, hexColor:String, hex:Object} | num: 当前滑块下标值, rgbColor: rgb颜色值【rgb(0,0,0)】, hex颜色值【##ff9800】,hex: R,G,B对应16进制hex值 |
调用实现
在需要引用的hml中element引入组件。
<element name="yg-slider" src="../../common/component/ygSlider/ygSlider"></element>
<div class="container">
<yg-slider
g-color="{{gColor}}"
step="{{step}}"
show-card="{{showCard}}"
@get-result="getResult"
></yg-slider>
</div>
js中的传参和绑定的事件。
import Log from "../../common/utils/log"
const lg = new Log('index页面'); // 可以传入一个值作为该页面的标识
export default {
data: {
gColor: [
'#FF0000',
'#FFE300',
'#74FF00',
'#00FFA6',
'#00FDFF',
'#0034FC',
'#4200FF',
'#BC00FF',
'#FD00FF',
'#FF0000'
],
step: 20,
showCard: true,
},
onInit() {
},
getResult(res){
lg.info(res._detail)
},
}
- gColor是渐变色值组,这里需要跟css样式里的一致。
- 本来尝试在内联样式绑定linear-gradient,切报错提示无法绑定渐变的内联样式
- 该问题已提Issues:https://gitee.com/openharmony/docs/issues/I5F8KC?from=project-issue。
实现思路
- 根据UI写好样式。
- 绑定滑块的触摸事件。
- 获取滑块条的长度和位置,和当前触摸的位置做比较,通过计算获取滑块所在的位置。
- 封装获取两颜色之间的渐变值组。
- 根据渐变值组总长度,设置为滑块的刻度值。
- 滑块滑动到某位置计算该位置刻度,得到渐变值组的对应色值,赋值给颜色卡片展示。
实现过程
1、根据UI写好页面和样式
ygSlider.hml页面:
<div class="container">
<div class="yg-slider" ref="ygSlider" id="ygSlider">
<div class="yg-slider-bg"></div>
<div class="yg-slider-button">
<div class="yg-slider-button-child"></div>
</div>
<div class="yg-slider-color"></div>
</div>
</div>
ygSlider.css:
.yg-slider{
width: 320px;
}
.yg-slider-bg{
background: linear-gradient(90deg, #FF0000, #FFE300, #74FF00, #00FFA6, #00FDFF, #0034FC, #4200FF, #BC00FF, #FD00FF,#FF0000);
width: 100%;
height: 4px;
border-radius: 16px;
}
.yg-slider-button{
/* 设置热区 */
position: absolute;
top: -16px;
transform: translateX(-18px);
left: 0%;
width: 36px;
height: 36px;
background-color: rgba(52,0,250,.5);
border-radius: 16px;
display: flex;
justify-content: center;
align-items: center;
}
.yg-slider-button-child{
border-radius: 8px;
width: 16px;
height: 16px;
background-color: rgba(255,255,255,1);
box-shadow: 0 0 1px 1px rgba(0,0,0,.2);
}
.yg-slider-color{
position: absolute;
top: -80px;
transform: translateX(-20px);
width: 40px;
height: 40px;
border-radius: 40px;
background-color: red;
}
2. 绑定滑块的触摸事件
ygSlider.hml:
<div class="yg-slider-button"
style="left: {{pct}}%;"
@touchstart="sliderStart"
@touchmove="sliderMove"
@touchend="sliderEnd"
>
ygSlider.js:
在sliderStart事件里,通过getBoundingClientRect()方法获取滑块条的宽高和位置。
sliderStart(){
const t = this.$refs.ygSlider.getBoundingClientRect();
this.startX = t.left;
this.endX = t.left + t.width;
this.sliderW = t.width;
},
在sliderMove事件里设置pct为滑块按钮的位置,并确保按钮不会超出滑块条
sliderMove(e){
let x = e.touches[0].globalX;
let s_x = x - this.startX;
if(x <= this.startX){
this.pct = 0;
} else if(x >= this.endX){
this.pct = 100;
} else {
this.pct = s_x * 100 / this.sliderW;
}
},
3、封装获取两颜色之间的渐变值组的方法
封装颜色渐变之间值方法,有备注。这个方法在之前的一个组件中就开始实现了,搞好需要就拿过来修改了一下。
传送门: HarmonyOS 基于JSAPI自定义封装渐变圆环进度条组件。
/**
* @description: 封装颜色渐变之间值
* @param {String} startColor 开始颜色hex
* @param {Number} endColor 结束颜色hex
* @param {Number} step 渐变精度
* @return {Array}
*/
gradientColor(startColor,endColor,step){
let startRGB = this.hexToRgb(startColor);//转换为rgb数组模式
let endRGB = this.hexToRgb(endColor);
let sR = (endRGB[0]-startRGB[0])/step;//总差值
let sG = (endRGB[1]-startRGB[1])/step;
let sB = (endRGB[2]-startRGB[2])/step;
var colorArr = [];
for(var i=0;i<step;i++){
//计算每一步的hex值
var hex = `rgb(${parseInt((sR*i+startRGB[0]))},${parseInt((sG*i+startRGB[1]))},${parseInt((sB*i+startRGB[2]))})`;
colorArr.push(hex);
}
return colorArr;
},
// 将hex表示方式转换为rgb表示方式(这里返回rgb数组模式)
hexToRgb(sColor){
var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
sColor = sColor.toLowerCase();
if(sColor && reg.test(sColor)){
if(sColor.length === 4){
var sColorNew = "#";
for(var i=1; i<4; i+=1){
sColorNew += sColor.slice(i,i+1).concat(sColor.slice(i,i+1));
}
sColor = sColorNew;
}
//处理六位的颜色值
var sColorChange = [];
for(let i=1; i<7; i+=2){
sColorChange.push(parseInt("0x"+sColor.slice(i,i+2)));
}
return sColorChange;
}else{
return sColor;
}
},
// 将rgb表示方式转换为hex表示方式
rgbToHex(rgb){
var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
if(/^(rgb|RGB)/.test(rgb)){
let aColor = rgb.replace(/(?:\(|\)|rgb|RGB)*/g,"").split(",");
let strHex = "#";
for(let i=0; i<aColor.length; i++){
let hex = this.getHexNumber(aColor[i]);
if(hex === "0"){
hex += hex;
}
strHex += hex;
}
if(strHex.length !== 7){
strHex = rgb;
}
return strHex;
}else if(reg.test(rgb)){
var aNum = rgb.replace(/#/,'').split('');
if(aNum.length === 6){
return rgb;
}else if(aNum.length === 3){
var numHex = "#";
for(let i=0; i<aNum.length; i+=1){
numHex += (aNum[i]+aNum[i]);
}
return numHex;
}
}else{
return rgb;
}
},
//将hexColor或者rgbColor转换hex的RGB值对象
colorToHex(rgb){
if(rgb && rgb.indexOf('#') > -1){
var aNum = rgb.replace(/#/,'').toUpperCase().split('');
if(aNum.length === 6){
return {
R: aNum[0] + aNum[1],
G: aNum[2] + aNum[3],
B: aNum[4] + aNum[5]
};
}else if(aNum.length === 3){
return {
R: aNum[0] + aNum[0],
G: aNum[1] + aNum[1],
B: aNum[2] + aNum[2]
};
} else {
return rgb;
}
} else if(/^(rgb|RGB)/.test(rgb)){
let aColor = rgb.replace(/(?:\(|\)|rgb|RGB)*/g,"").split(",");
let R = this.getHexNumber(aColor[0]);
let G = this.getHexNumber(aColor[1]);
let B = this.getHexNumber(aColor[2]);
return {R,G,B};
} else {
return rgb;
}
},
getHexNumber(str){
if(typeof str === 'string'){
return Number(str).toString(16).padStart(2,'0').toUpperCase()
} else {
return str;
}
}
因为传进来的颜色组不止两个,所以做了一个循环处理。
onPageShow(){
this.setColorList();
},
//将传进来的颜色生成一个渐变色值组
setColorList(){
for(let i = 0; i < this.gColor.length - 1; i++){
let res = this.gradientColor(this.gColor[i], this.gColor[i+1], this.step)
this.colorList.push(res)
}
// 设置滑块可以展示的最大值
this.maxValue = this.colorList.length - 1;
// 展示卡片默认显示数组里的第一个色值
this.nowColor = this.colorList[0]
},
4、根据渐变值组总长度,设置为滑块的刻度值
sliderMove(e){
let x = e.touches[0].globalX;
let s_x = x - this.startX;
if(x <= this.startX){
this.pct = 0;
} else if(x >= this.endX){
this.pct = 100;
} else {
this.pct = s_x * 100 / this.sliderW;
}
// 计算当前的刻度值
let r = this.getValue(s_x);
// 设置滑块按钮滑到所在的位置的的色值
this.nowColor = this.colorList[r];
//最后将每次滑块所变更的值都抛出给父组件去调用做相应的处理
this.$emit('getResult', {
num: r,
rgbColor: this.nowColor,
hexColor: this.rgbToHex(this.nowColor),
hex: this.colorToHex(this.nowColor),
})
},
sliderEnd(e){
let x = e.touches[0].globalX;
let s_x = x - this.startX;
let r = this.getValue(s_x);
this.nowColor = this.colorList[r];
this.$emit('getResult', {
num: r,
rgbColor: this.nowColor,
hexColor: this.rgbToHex(this.nowColor),
hex: this.colorToHex(this.nowColor),
})
},
// 获取当前刻度值
getValue(s_x){
let r = Math.ceil(this.maxValue * s_x / this.sliderW);
if(r <= 0){
r = 0;
} else if(r >= this.maxValue) {
r = this.maxValue;
}
return r;
},
代码地址
#夏日挑战赛# openHarmony - 基于ArkUI(JS)实现色相滑块组件。
总结
难点:逻辑都比较简单,主要难点在于就是计算两颜色值之间的渐变色值组。
和计算当前刻度,这里需要注意三个值:
- 滑块按钮的当前位置 0% -100%
- 滑块条的长度,比如是320px
- 滑块的刻度
三者之间需要进行计算转换。