好吧,这个答案已经成为它自己的野兽。许多新版本,它变得越来越愚蠢。非常感谢所有为这个答案做出贡献的人。但是,为了保持对大众的简单性。我将这个答案演变的所有版本/历史存档到我的github。并在StackOverflow上用最新版本从头开始。特别感谢Mike 'Pomax' Kamermans的这个版本。他给了我新的数学。
此函数 () 将采用十六进制或 RGB 网页颜色。 可以将其着色得更暗或更亮,或者将其与第二种颜色混合,也可以将其直接传递,但从十六进制转换为 RGB (Hex2RGB) 或 RGB 转换为十六进制 (RGB2Hex)。所有这些都没有你甚至知道你正在使用什么颜色格式。pSBC
pSBC
它运行得非常快,可能是最快的,特别是考虑到它的许多功能。这是酝酿已久的。在我的github上查看整个故事。如果你想要绝对最小和最快的方式来着色或混合,请参阅下面的微功能,并使用其中一个2线速度恶魔。它们非常适合激烈的动画,但这里的这个版本对于大多数动画来说已经足够快了。
此函数使用日志混合或线性混合。但是,它不会转换为HSL以适当地变亮或变暗颜色。因此,此函数的结果将与使用 HSL 的那些大得多、速度慢得多的函数不同。
jsFiddle with pSBC
github > pSBC Wiki
特征:
- 自动检测并接受字符串形式的标准十六进制颜色。例如:或 .
"#AA6622"
"#bb551144"
- 自动检测并接受字符串形式的标准 RGB 颜色。例如:或 .
"rgb(123,45,76)"
"rgba(45,15,74,0.45)"
- 按百分比将颜色着色为白色或黑色。
- 按百分比将颜色混合在一起。
- 同时进行Hex2RGB和RGB2Hex转换,还是单独转换。
- 接受 3 位数字(或 4 位带 alpha)十六进制颜色代码,格式为 #RGB(或 #RGBA)。它将扩展它们。例如:变为 .
"#C41"
"#CC4411"
- 接受和(线性)混合 Alpha 通道。如果(从)颜色或(到)颜色具有 Alpha 通道,则返回的颜色将具有 Alpha 通道。如果两种颜色都有一个 Alpha 通道,则返回的颜色将是使用给定百分比的两个 Alpha 通道的线性混合(就像它是普通颜色通道一样)。如果两种颜色中只有一种具有 Alpha 通道,则此 Alpha 将直接传递到返回的颜色。这允许人们混合/着色透明颜色,同时保持透明度水平。或者,如果透明度级别也应混合,请确保两种颜色都具有 Alpha。着色时,它将直接通过 Alpha 通道。如果您想要的基本着色同时为 Alpha 通道着色,请使用 或 作为您的(到)颜色(或其十六进制等效项)。对于 RGB 颜色,返回颜色的 Alpha 通道将四舍五入到小数点后 3 位。
c0
c1
rgb(0,0,0,1)
rgb(255,255,255,1)
c1
- 使用混合时,RGB2Hex 和 Hex2RGB 转换是隐式的。无论(来自)颜色如何;返回的颜色将始终采用(to)颜色的颜色格式(如果存在)。如果没有(到)颜色,则作为颜色传入,它将着色并转换任何颜色。如果只需要转换,则也以百分比()的形式传入。如果省略颜色或传入非 -,则不会转换。
c0
c1
c1
'c'
c1
c0
0
p
c1
string
- 辅助函数也添加到全局。 可以传递十六进制或 RGB 颜色,并返回包含此颜色信息的对象。其形式为:{r: XXX, g: XXX, b: XXX, a: X.XXX}。其中 , 和 的范围为 0 到 255。当没有 alpha 时:为 -1。否则:范围为 0.000 到 1.000。
pSBCr
.r
.g
.b
.a
.a
- 对于 RGB 输出,当具有 Alpha 通道的颜色传递到 (from) 和/或 (to) 时,它会输出。
rgba()
rgb()
c0
c1
- 添加了次要错误检查。这并不完美。它仍然可能崩溃或产生抖动。但它会抓住一些东西。基本上,如果结构在某些方面是错误的,或者如果百分比不是一个数字或超出范围,它将返回 。例如:,它认为是有效的颜色。删除以 结尾的四行以删除此功能,并使其更快、更小。
null
pSBC(0.5,"salt") == null
#salt
return null;
- 使用日志混合。传入(第 4 个参数)以使用线性混合。
true
l
法典:
// Version 4.0
const pSBC=(p,c0,c1,l)=>{
let r,g,b,P,f,t,h,i=parseInt,m=Math.round,a=typeof(c1)=="string";
if(typeof(p)!="number"||p<-1||p>1||typeof(c0)!="string"||(c0[0]!='r'&&c0[0]!='#')||(c1&&!a))return null;
if(!this.pSBCr)this.pSBCr=(d)=>{
let n=d.length,x={};
if(n>9){
[r,g,b,a]=d=d.split(","),n=d.length;
if(n<3||n>4)return null;
x.r=i(r[3]=="a"?r.slice(5):r.slice(4)),x.g=i(g),x.b=i(b),x.a=a?parseFloat(a):-1
}else{
if(n==8||n==6||n<4)return null;
if(n<6)d="#"+d[1]+d[1]+d[2]+d[2]+d[3]+d[3]+(n>4?d[4]+d[4]:"");
d=i(d.slice(1),16);
if(n==9||n==5)x.r=d>>24&255,x.g=d>>16&255,x.b=d>>8&255,x.a=m((d&255)/0.255)/1000;
else x.r=d>>16,x.g=d>>8&255,x.b=d&255,x.a=-1
}return x};
h=c0.length>9,h=a?c1.length>9?true:c1=="c"?!h:false:h,f=this.pSBCr(c0),P=p<0,t=c1&&c1!="c"?this.pSBCr(c1):P?{r:0,g:0,b:0,a:-1}:{r:255,g:255,b:255,a:-1},p=P?p*-1:p,P=1-p;
if(!f||!t)return null;
if(l)r=m(P*f.r+p*t.r),g=m(P*f.g+p*t.g),b=m(P*f.b+p*t.b);
else r=m((P*f.r**2+p*t.r**2)**0.5),g=m((P*f.g**2+p*t.g**2)**0.5),b=m((P*f.b**2+p*t.b**2)**0.5);
a=f.a,t=t.a,f=a>=0||t>=0,a=f?a<0?t:t<0?a:a*P+t*p:0;
if(h)return"rgb"+(f?"a(":"(")+r+","+g+","+b+(f?","+m(a*1000)/1000:"")+")";
else return"#"+(4294967296+r*16777216+g*65536+b*256+(f?m(a*255):0)).toString(16).slice(1,f?undefined:-2)
}
用法:
// Setup:
let color1 = "rgb(20,60,200)";
let color2 = "rgba(20,60,200,0.67423)";
let color3 = "#67DAF0";
let color4 = "#5567DAF0";
let color5 = "#F3A";
let color6 = "#F3A9";
let color7 = "rgb(200,60,20)";
let color8 = "rgba(200,60,20,0.98631)";
// Tests:
/*** Log Blending ***/
// Shade (Lighten or Darken)
pSBC ( 0.42, color1 ); // rgb(20,60,200) + [42% Lighter] => rgb(166,171,225)
pSBC ( -0.4, color5 ); // #F3A + [40% Darker] => #c62884
pSBC ( 0.42, color8 ); // rgba(200,60,20,0.98631) + [42% Lighter] => rgba(225,171,166,0.98631)
// Shade with Conversion (use "c" as your "to" color)
pSBC ( 0.42, color2, "c" ); // rgba(20,60,200,0.67423) + [42% Lighter] + [Convert] => #a6abe1ac
// RGB2Hex & Hex2RGB Conversion Only (set percentage to zero)
pSBC ( 0, color6, "c" ); // #F3A9 + [Convert] => rgba(255,51,170,0.6)
// Blending
pSBC ( -0.5, color2, color8 ); // rgba(20,60,200,0.67423) + rgba(200,60,20,0.98631) + [50% Blend] => rgba(142,60,142,0.83)
pSBC ( 0.7, color2, color7 ); // rgba(20,60,200,0.67423) + rgb(200,60,20) + [70% Blend] => rgba(168,60,111,0.67423)
pSBC ( 0.25, color3, color7 ); // #67DAF0 + rgb(200,60,20) + [25% Blend] => rgb(134,191,208)
pSBC ( 0.75, color7, color3 ); // rgb(200,60,20) + #67DAF0 + [75% Blend] => #86bfd0
/*** Linear Blending ***/
// Shade (Lighten or Darken)
pSBC ( 0.42, color1, false, true ); // rgb(20,60,200) + [42% Lighter] => rgb(119,142,223)
pSBC ( -0.4, color5, false, true ); // #F3A + [40% Darker] => #991f66
pSBC ( 0.42, color8, false, true ); // rgba(200,60,20,0.98631) + [42% Lighter] => rgba(223,142,119,0.98631)
// Shade with Conversion (use "c" as your "to" color)
pSBC ( 0.42, color2, "c", true ); // rgba(20,60,200,0.67423) + [42% Lighter] + [Convert] => #778edfac
// RGB2Hex & Hex2RGB Conversion Only (set percentage to zero)
pSBC ( 0, color6, "c", true ); // #F3A9 + [Convert] => rgba(255,51,170,0.6)
// Blending
pSBC ( -0.5, color2, color8, true ); // rgba(20,60,200,0.67423) + rgba(200,60,20,0.98631) + [50% Blend] => rgba(110,60,110,0.83)
pSBC ( 0.7, color2, color7, true ); // rgba(20,60,200,0.67423) + rgb(200,60,20) + [70% Blend] => rgba(146,60,74,0.67423)
pSBC ( 0.25, color3, color7, true ); // #67DAF0 + rgb(200,60,20) + [25% Blend] => rgb(127,179,185)
pSBC ( 0.75, color7, color3, true ); // rgb(200,60,20) + #67DAF0 + [75% Blend] => #7fb3b9
/*** Other Stuff ***/
// Error Checking
pSBC ( 0.42, "#FFBAA" ); // #FFBAA + [42% Lighter] => null (Invalid Input Color)
pSBC ( 42, color1, color5 ); // rgb(20,60,200) + #F3A + [4200% Blend] => null (Invalid Percentage Range)
pSBC ( 0.42, {} ); // [object Object] + [42% Lighter] => null (Strings Only for Color)
pSBC ( "42", color1 ); // rgb(20,60,200) + ["42"] => null (Numbers Only for Percentage)
pSBC ( 0.42, "salt" ); // salt + [42% Lighter] => null (A Little Salt is No Good...)
// Error Check Fails (Some Errors are not Caught)
pSBC ( 0.42, "#salt" ); // #salt + [42% Lighter] => #a5a5a500 (...and a Pound of Salt is Jibberish)
// Ripping
pSBCr ( color4 ); // #5567DAF0 + [Rip] => [object Object] => {'r':85,'g':103,'b':218,'a':0.941}
下图将有助于显示两种混合方法的差异:
微功能
如果你真的想要速度和大小,你将不得不使用RGB而不是十六进制。RGB更直接,更简单,十六进制写得太慢,并且对于简单的双行代码来说有太多的风格(即它可能是3,4,6或8位十六进制代码)。您还需要牺牲一些功能,没有错误检查,没有HEX2RGB或RGB2HEX。同样,您需要为颜色混合数学选择一个特定的函数(基于下面的函数名称),以及是否需要着色或混合。这些函数支持 Alpha 通道。当两种输入颜色都有 Alpha 时,它将线性混合它们。如果两种颜色中只有一种具有 Alpha,它将直接传递到生成的颜色。以下是两个非常快速和小巧的衬垫功能:
const RGB_Linear_Blend=(p,c0,c1)=>{
var i=parseInt,r=Math.round,P=1-p,[a,b,c,d]=c0.split(","),[e,f,g,h]=c1.split(","),x=d||h,j=x?","+(!d?h:!h?d:r((parseFloat(d)*P+parseFloat(h)*p)*1000)/1000+")"):")";
return"rgb"+(x?"a(":"(")+r(i(a[3]=="a"?a.slice(5):a.slice(4))*P+i(e[3]=="a"?e.slice(5):e.slice(4))*p)+","+r(i(b)*P+i(f)*p)+","+r(i(c)*P+i(g)*p)+j;
}
const RGB_Linear_Shade=(p,c)=>{
var i=parseInt,r=Math.round,[a,b,c,d]=c.split(","),P=p<0,t=P?0:255*p,P=P?1+p:1-p;
return"rgb"+(d?"a(":"(")+r(i(a[3]=="a"?a.slice(5):a.slice(4))*P+t)+","+r(i(b)*P+t)+","+r(i(c)*P+t)+(d?","+d:")");
}
const RGB_Log_Blend=(p,c0,c1)=>{
var i=parseInt,r=Math.round,P=1-p,[a,b,c,d]=c0.split(","),[e,f,g,h]=c1.split(","),x=d||h,j=x?","+(!d?h:!h?d:r((parseFloat(d)*P+parseFloat(h)*p)*1000)/1000+")"):")";
return"rgb"+(x?"a(":"(")+r((P*i(a[3]=="a"?a.slice(5):a.slice(4))**2+p*i(e[3]=="a"?e.slice(5):e.slice(4))**2)**0.5)+","+r((P*i(b)**2+p*i(f)**2)**0.5)+","+r((P*i(c)**2+p*i(g)**2)**0.5)+j;
}
const RGB_Log_Shade=(p,c)=>{
var i=parseInt,r=Math.round,[a,b,c,d]=c.split(","),P=p<0,t=P?0:p*255**2,P=P?1+p:1-p;
return"rgb"+(d?"a(":"(")+r((P*i(a[3]=="a"?a.slice(5):a.slice(4))**2+t)**0.5)+","+r((P*i(b)**2+t)**0.5)+","+r((P*i(c)**2+t)**0.5)+(d?","+d:")");
}
需要更多信息?阅读 github 上的完整文章。
铂
(附言如果有人对另一种混合方法有数学计算,请分享。