在前端页面设计中,为追求美观和丰富性,UED 通常会选择将某些元素设置为渐变色。渐变色固然美观,但对于刚接触的渐变的前端开发者,渐变色的设置却让并不容易上手。本文就来探讨下如何使用在 CSS 、SVG 以及 Canvas 中使用渐变 。
一、CSS 渐变
首先我们来了解前端页面开发中最常用的CSS渐变:
CSS 渐变由 <gradient> 数据类型表示,它是 <image> 的一种特殊类型,由两种或多种颜色之间的渐变过渡构成。
包含如下三种类型:
- 线性渐变:由
linear-gradient()函数创建 - 径向渐变:由
radial-gradient()函数创建 - 锥形渐变:由
conic-gradient()函数创建
此外,还可以使用 repeating-linear-gradient() 和 repeating-conic-gradient() 函数创建重复渐变。
渐变可以在任何使用 <image> 的地方使用,例如如下 CSS 属性:
1.1 线性渐变 - linear-gradient()
线性渐变创建了创建一个由两种或多种颜色沿一条直线进行线性过渡的图像,通过linear-gradient()函数实现。
linear-gradient()函数最基本的使用方式就是指定两种颜色。这些被称为色标(color stop)。至少指定两个色标,也可以指定任意数量。例如:
linear-gradient(#afb4db, #f391a9);
当然该函数也提供了其他参数,用于创建更加细致的渐变。
1.1.1 渐变方向
默认情况下,线性渐变的方向是从上到下,我们通过 <side-or-corner> 或 <angle> 来指定渐变方向:
-
<side-or-corner>:渐变线的起始点位置。包含to和最多两个关键字(默认为to bottom):- 一个指定水平位置(
left或right) - 一个指定竖直位置(
top或bottom)
- 一个指定水平位置(
-
<angle>:渐变线的方向的角度。0deg等价于to top,增加值相当于顺时针旋转(如果设置为负值,则相当于逆时针旋转)。角度有如下几种单位:deg:度,一个完整的圆是360deg。grad:百分度,一个完整的圆是400grad。rad:弧度,一个完整的圆是 2π 弧度,约等于6.2832rad。1rad是 180/π 度。turn: 圈数,一个完整的圆是1turn。
有关渐变方向设置的示例如下:
// 未指定时,则默认为 `to bottom`,转换成渐变角度即 180deg
linear-gradient(#afb4db, #f391a9);
// 通过 <side-or-corner> 指定
linear-gradient(to top, #afb4db, #f391a9);
// 通过 <side-or-corner> 指定,两个关键字(只能水平方向和垂直方向各一个)
linear-gradient(to top right, #afb4db, #f391a9);
// 通过 <angle> 指定
linear-gradient(45deg, #afb4db, #f391a9);
注意:
<side-or-corner>和<angle>只能设置其中一个,否则会导致渐变不生效。
<side-or-corner>和<angle>之间可以相互转换:
to top:等价于渐变角度0deg
to right:等价于渐变角度90deg
to bottom:等价于渐变角度180deg
to left:等价于渐变角度270deg
to top right:等价于渐变角度45deg...
1.1.2 色标
我们可以通过 <linear-color-stop>来设置色标的颜色和位置,其语法如下:
// 色标颜色,随后是一个或两个可选的色标位置
<linear-color-stop> = <color> <length-percentage>?
- 使用多种颜色
渐变至少需要指定两种颜色,但不仅限于两种颜色。默认情况下,所设置颜色会均匀分布在渐变路径中。例如:
// 四种渐变色
linear-gradient(90deg, #afb4db, #f391a9, #fedcbd, #65c294);
// 三种渐变色
linear-gradient(90deg, #afb4db, #f391a9, #fedcbd);
- 色标位置
色标位置可以通过沿渐变轴的 <percentage> 值或 <length> 值来指定。
以百分数为例,0% 表示起始点,而 100% 表示终点(如果需要的话你也可以设置这个范围之外的其他值来达到你想要的效果)。默认情况下,第一个色标在 0% 处,最后一个色标在 100%,其他的色标则位于其相邻的两个色标中间。
// 默认
linear-gradient(90deg, #afb4db, #f391a9 40px, #fedcbd 80%, #65c294);
// 指定色标位置
linear-gradient(90deg, #afb4db, #f391a9 40px, #fedcbd 80%, #65c294);
我们还可以给某个色标指定两个位置值,这样的效果就是该区域只展示该颜色,如下渐变中,[30%, 80%] 这个区域只会显示 #f391a9 色值
linear-gradient(90deg, #afb4db, #f391a9 30% 80%, #fedcbd)
此外,我们可以给某两个相邻色标设置相同的位置,这样会在两种颜色之间创建一条硬线,分割渐变:
linear-gradient(90deg, #afb4db, #f391a9 50%, #fedcbd 50%, #65c294)
1.1.3 插值提示
默认情况下,渐变会平滑地从一种颜色过渡到另一种颜色。你可以通过设置<color-hint>来将渐变的中心点移动到指定位置。
<color-hint>:插值提示,定义渐变色在相邻色标之间的渐变过程。其中的长度定义了渐变色应在两个色标之间的哪个点到达颜色过渡的中点。如果省略该值,颜色过渡的中点就是两个色标之间的中点。
// 默认为色标之间的中点
linear-gradient(90deg, #afb4db, #f391a9);
// 设置过渡中点为10%
linear-gradient(90deg, #afb4db, 10%, #f391a9);
总的来说, 使用 linear-gradient() 创建线性渐变时,主要设置渐变方向和色标,其形式语法大致如下:
// 渐变方向和色标
linear-gradient( [ <angle> | to <side-or-corner> ]? , <color-stop-list> )
// 色标列表:色标 插值提示 色标 ...
<color-stop-list> =
<linear-color-stop> , [ <linear-color-hint>? , <linear-color-stop> ]#?
// 色标: 色标颜色 和 色标位置
<linear-color-stop> =
<color> (<length>|<percentage>)?
1.2 径向渐变 - radial-gradient()
径向渐变(Radial Gradient) 是一种颜色从一个中心点向外扩散的渐变方式。与线性渐变不同,径向渐变的颜色变化是围绕一个或多个中心点进行的,形成圆形或椭圆形的渐变效果。通过radial-gradient()函数实现。
与线性渐变一样,radial-gradient()函数最基本的使用方式也是指定两种颜色。默认情况下,渐变的中心点是 50% 50% 的位置,且渐变是椭圆的以匹配盒的比例。
radial-gradient(#afb4db, #f391a9)
当然,radial-gradient()也支持如下参数,用于设置形状、大小、位置等,其语法如下:
<radial-gradient-syntax> =
[ <radial-shape> || <radial-size> ]? [ at <position> ]? , <color-stop-list>
1.2.1 渐变中心位置
<position>:渐变中心的位置,默认为 center,即图层的中心。该参数与设置 background-position 的方式相同,可以使用一到四个值进行定义,值可以是 top、left、bottom、right、center 等关键字,也可以是<length> 或 <percentage>。其形式语法如下所示:
<position> =
// 一个值
[ left | center | right | top | bottom | <length-percentage> ] |
// 两个关键字
[ left | center | right ] && [ top | center | bottom ] |
// 一个关键字和length-percentage
[ left | center | right | <length-percentage> ] [ top | center | bottom | <length-percentage> ] |
// 关键字和偏移量
[ [ left | right ] <length-percentage> ] && [ [ top | bottom ] <length-percentage> ]
简单来说,就是一以图层左上角为原点,建立如下直角坐标系,设置渐变中心在该坐标系中的 X, Y 坐标。
一个值的语法: :
- 关键字
center:默认值,即图层中心。 - 关键字
top、left、bottom、right中的一个:用来指定把这个项目放在哪一个边界。另一个维度被设置成 50%,所以这个项目被放在指定边界的中间位置。 <length>或<percentage>:指定相对于左边界的 x 坐标,y 坐标被设置成 50%。
// center
radial-gradient(at center, #65c294, #f391a9);
// left
radial-gradient(at left, #65c294, #f391a9);
// top
radial-gradient(at top, #65c294, #f391a9);
// 30%
radial-gradient(at 30%, #65c294, #f391a9);
两个值的语法: 一个定义 x 坐标,另一个定义 y 坐标。
- 关键字
top、left、bottom、right、center:- 如果一个值为
left或right,那么这个值定义 x 轴位置,另一个值定义 y 轴位置。 - 如果一个值为
top或bottom,那么这个值定义 y 轴位置,另一个值定义 x 轴位置。
- 如果一个值为
注意: 两个值都是关键字时:
- 只允许一个值是
left或right,另一个值是top或bottom- 无需关注书写顺序,写成
top left或left top其产生的效果是相同的- 如果其中一个关键字为
center, 等价于该轴位置设置为50%
<length>或<percentage>:- 如果另一个值是
left或right,则该值定义相对于顶部边界的 Y。 - 如果另一个值是
top或bottom,则该值定义相对于左边界的 X。 - 如果两个值都是
<length>或<percentage>值,则第一个定义 X,第二个定义 Y。
- 如果另一个值是
注意: 使用
<length>或<percentage>与关键字配对时顺序非常重要,定义 X 的值放在前面,然后是定义 Y 的值,right 20px和20px right的效果是不相同的,前者有效但后者无效。
// left top
radial-gradient(at left top, #65c294, #f391a9);
// left 80%
radial-gradient(at left 80%, #65c294, #f391a9);
// 50px bottom
radial-gradient(at 50px bottom, #65c294, #f391a9);
// 30% 70%
radial-gradient(at 30% 70%, #65c294, #f391a9);
四个值的语法: 第一个和第三个值是定义 X 和 Y 的关键字值。第二个和第四个值是前面 X 和 Y 关键字值的偏移量:
- 第一个值和第三个值是关键字值
top、left、bottom、right之一。- 如果设置为
left或right,则定义了 X。 - 如果设置为
top或bottom,则定义了 Y,另一个关键字值定义了 X。
- 如果设置为
- 第二个和第四个值是
<length>或<percentage>。第二个值是第一个关键字的偏移量。第四个值是第二个关键字的偏移量。
// left 20% top 20%
radial-gradient(at left 20% top 20%, #65c294, #f391a9);
// right 20% bottom 20%
radial-gradient(at right 20% bottom 20%, #65c294, #f391a9);
1.2.2 渐变形状
<ending-shape>:渐变的结束形状。值可以是:
circle:圆形,渐变的形状是一个半径不变的正圆ellipse:椭圆,渐变形状是轴对称椭圆(默认值)
// ellipse
radial-gradient(#65c294, #f391a9);
// circle
radial-gradient(circle, #65c294, #f391a9);
1.2.3 渐变大小
<size>:确定渐变形状的大小。如果省略,则默认为最远角(farthest-corner)。它可以显式给出,也可以通过关键字给出。就关键字定义而言,渐变框边缘应视为向两个方向无限延伸,而不是有限线段。对于它们的 <size>,圆形和椭圆渐变都接受以下关键字:
| 关键字 | 描述 |
|---|---|
closest-side | 渐变结束形状如果是圆形,与容器距离渐变中心点最近的一边相切,如果是椭圆,则与距离渐变中心点最近的垂直和水平边相切。 |
closest-corner | 渐变结束形状与容器距离渐变中心点最近的一个角相交。 |
farthest-side | 类似于 closest-side,但是结束形状与容器距离渐变中心点最远的一边(或最远的垂直和水平边)相切。 |
farthest-corner | 默认值,渐变的结束形状与容器距离渐变中心点最远的一个角相交。 |
如果 <ending-shape> 被指定为 circle,则可以为大小指定一个 <length> 值,它明确了圆的半径。负值是无效的。
如果 <ending-shape> 被指定为 ellipse,则可以使用带有两个值的 <length-percentage> 来指定椭圆的大小(百分比值是相对于渐变框在相应维度上的大小的。负值是无效的。)。
- 第一个值表示水平半径,
- 第二个值表示垂直半径。
如果 <ending-shape> 关键词被省略,渐变形状会由给定的大小决定。一个 <length> 产生圆,两个 <length-percentage> 单位的值产生椭圆。一个 <percentage> 值是无效的。
1.2.3 色标(color stop)的 插值提示(color-hint)
这两个设置与线性渐变相同,这里就不再赘述。
最后再多嘴一句,设置渐变形状、大小、位置时,一定要严格按照 <radial-shape> <radial-size> at <position> 的顺序书写,否则会导致渐变失效。举个例子,我们想创建一个渐变中心在[30%, 50%] 处,且渐变半径为 10px 的 circle 渐变,我们应当按如下格式书写:
radial-gradient(circle 10px at 30% 50%, ...);
1.3 锥形渐变 - conic-gradient()
锥形渐变(Conical Gradient) 是一种特殊的颜色渐变模式,它从一个中心点开始,以360度环绕的方式向外扩散,形成一个类似于锥体展开后的平面图案。由conic-gradient()函数创建。
基本的使用方式也是指定两种颜色:
conic-gradient(#65c294, #f391a9)
conic-gradient()函数的形式语法如下:
<conic-gradient-syntax> =
[ [ [ from <angle> ]? [ at <position> ]? ] || <color-interpolation-method> ]? , <angular-color-stop-list>
1.3.1 起始角度
<angle>: 在 from 关键字之后,以角度作为其值,定义顺时针方向的渐变旋转。即:定义渐变从哪个角度开始
1.2.1 渐变中心位置
与 radial-gradient() 的 postion 设置相同,这里不过多赘述。
1.2.3 色标和插值提示
与线性渐变和径向渐变不同的是,锥形渐变的色标位置值通过沿着渐变圆周轴的 <angle> 或者 <percentage>来指定,插值提示也亦是如此。例如:
conic-gradient(#65c294, #afb4db 90deg, #f391a9)
conic-gradient(#65c294, #afb4db 50%, #f391a9)
1.4 重复渐变
linear-gradient()、radial-gradient() 和 conic-gradient() 函数不支持自动重复的色标。但是,repeating-linear-gradient()、repeating-radial-gradient() 和 repeating-conic-gradient() 函数可以用于提供此功能。
1.5 文字渐变 *
前文我们提到,渐变可以在background-image、list-style-image、border-image cursor等使用 <image> 的地方使用。但是在日常开发中我们可能会遇到给某些图标设置渐变色的场景,但是有些图标是通过 iconfont 的形式给出的。我们知道 iconfont 是字体图标,可以给他设置字体相关的属性,而字体的颜色属性color并不支持渐变。那如何给文字设置渐变色呢?
iconfont,即图标字体,是一种将图标以字体形式呈现的技术。通过将图标转化为字体文件,可以替代传统的图片文件来展示图标、特殊字体等元素。
既然无法直接设置,那就从背景的角度出发。CSS 背景相关属性中有个 background-clip ,可以定义背景的绘制区域,具体值如下:
border-box:背景延伸至边框外沿(但是在边框下层)。padding-box:背景延伸至内边距(padding)外沿。不会绘制到边框处。content-box:背景被裁剪至内容区(content box)外沿。text:背景被裁剪成文字的前景色。
当我们将 background-clip 属性设置为 text 时,也就意味着背景只会在文字区域绘制,这时我们将文字颜色设置为透明(transparent), 那么文字颜色也就跟随背景色了。所以通过这种方式,我们就能给文字设置渐变色了:
// 字体颜色透明
color: transparent;
// 只绘制文字区域
-webkit-background-clip: text; /* WebKit 浏览器专用 */
background-clip: text;
// 设置渐变色
background-image: linear-gradient(#65c294, #afb4db);
关于文字渐变的实现方式需要注意的是
background-clip: text目前还是实验性功能,使用时需要考虑浏览器的兼容性。
除了 iconfont 之外,日常开发中图标也经常会以svg的形式给出。那么下面,我们就看看如何在 svg 中定义渐变色吧。
二、SVG 渐变
在 SVG 中,可以使用 <linearGradient> 或 <radialGradient> 元素来创建渐变效果。这些渐变可以应用于各种 SVG 图形元素(包括路径、矩形、圆形等),以对图形元素进行填充或描边。
2.1 <linearGradient>
SVG 中的线性渐变通过<linearGradient>元素用来定义,其基本使用如下:
<svg
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<!-- 定义线性渐变 -->
<linearGradient id="myGradient">
<stop offset="0%" stop-color="#afb4db" />
<stop offset="100%" stop-color="#f391a9" />
</linearGradient>
</defs>
<!-- 使用线性渐变 -->
<rect width="100" height="100" fill="url('#myGradient')" />
</svg>
我们需要先在<def>中定义一个渐变,然后在需要使用的地方通过url('#id') 的形式引用即可。
<linearGradient>元素的也提供了如下属性,使得渐变更加丰富多彩。
渐变起点、终点 (x1, y1, x2, y2)
linearGradient 元素支持通过 x1, y1, x2, y2 属性来定义了渐变的 起点(x1, y1) 和 终点(x2, y2)。
这些值可以是百分比(默认相对于渐变对象的边界框)或者是绝对单位(如 px, cm, mm 等)。
起点(x1, y1) 和 终点(x2, y2) 的默认值如下:
- x1: 默认值为
0%。 - y1: 默认值为
0%。 - x2: 默认值为
100%。 - y2: 默认值为
0%。
这意味着默认情况下会产生一个从左到右的水平渐变效果。
当然,我们也可以通过定义起始点坐标来定义渐变方向,如下定义了一个沿对角线方向的渐变:
<linearGradient id="myGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#afb4db" />
<stop offset="100%" stop-color="#f391a9" />
</linearGradient>
这与CSS 中 linear-gradient()函数设置渐变方向的方式有所不同,但是两者之间也可通过某种方式进行转换,具体在本文第四节中介绍。
渐变转换 (gradientTransform)
gradientTransform 属性用于定义可选附加变换,例如旋转、倾斜、缩放等。具体取值可见transform-function。
<linearGradient id="myGradient" gradientTransform="rotate(90)">
<stop offset="0%" stop-color="#afb4db" />
<stop offset="100%" stop-color="#f391a9" />
</linearGradient>
渐变坐标系(gradientUnits)
gradientUnits 属性定义渐变元素上指定的属性所使用的坐标系,具体取值如下:
userSpaceOnUse:此值表示属性表示在引用渐变元素时采用当前用户坐标系(可以理解为全局坐标系)。百分比表示相对于当前 SVG 视口的宽高。objectBoundingBox(默认值):该值表示使用应用渐变的元素的边界框。百分比表示相对于当前元素的宽高。
spreadMethod
控制如何绘制超出渐变向量范围的颜色,具体取值如下:
pad: 渐变的最终颜色填充渐变边缘以外的形状。reflect: 渐变在其边缘以外反向重复。repeat:渐变在其边缘以外按原始顺序重复。
<linearGradient id="myGradient2" x1="20%" x2="40%" spreadMethod="pad">
<stop offset="0%" stop-color="#afb4db" />
<stop offset="100%" stop-color="#f391a9" />
</linearGradient>
<linearGradient id="myGradient2" x1="20%" x2="40%" spreadMethod="reflect">
<stop offset="0%" stop-color="#afb4db" />
<stop offset="100%" stop-color="#f391a9" />
</linearGradient>
<linearGradient id="myGradient2" x1="20%" x2="40%" spreadMethod="repeat">
<stop offset="0%" stop-color="#afb4db" />
<stop offset="100%" stop-color="#f391a9" />
</linearGradient>
注意:渐变向量范围指的是由 x1, y1, x2, y2 指定的范围。
渐变色标 <stop>
stop元素是<linearGradient>元素的子元素,用于定义渐变色标。包含以下属性:
-
offset: 定义色标的位置,可以是百分比或小数值。 -
stop-color: 定义该色标的颜色。 -
stop-opacity: 定义该色标的不透明度。
2.1 <radialGradient>
SVG 中的径向渐变通过<radialGradient>元素用来定义,其基本使用如下:
<svg
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<radialGradient id="radialGradient">
<stop offset="0%" stop-color="#afb4db" />
<stop offset="100%" stop-color="#f391a9" />
</radialGradient>
</defs>
<circle cx="100" cy="100" r="100" fill="url('#radialGradient')" />
</svg>
<radialGradient>元素的具体属性如下:
-
起点圆
-
终点圆
有关设置不同起点圆和终点圆所产生的具体效果可见3.2小节,与 Canvas 类似。
三、Canvas 渐变
网页上绘制图形的技术中,除了上述的 SVG 之外,另外一个最常用的技术就是 canvas 了。canvas 中也支持线性或者径向的渐变来填充或描边。
3.1 线性渐变
Canvas 中可以通过 createLinearGradient()方法创建一个线性渐变。
该方法需要指定四个参数,分别表示渐变线段的起点和终点:
该方法会返回一个 CanvasGradient 对象。
我们可以调用该对象上的 addColorStop() 方法,为给定的 canvas 渐变添加一个由偏移值(offset)和颜色值(color)指定的色标。
该方法参数如下:
基本使用如下:
function draw() {
var ctx = document.getElementById('canvas').getContext("2d");
// Create gradients
var lingrad = ctx.createLinearGradient(0, 0, 0, 100);
lingrad.addColorStop(0, "#afb4db");
lingrad.addColorStop(1, "#f391a9");
var lingrad2 = ctx.createLinearGradient(0, 50, 0, 95);
lingrad2.addColorStop(0, "#000");
lingrad2.addColorStop(1, "#fff");
// assign gradients to fill and stroke styles
ctx.fillStyle = lingrad;
ctx.strokeStyle = lingrad2;
// draw shapes
ctx.fillRect(0, 0, 100, 100);
ctx.strokeRect(25, 25, 50, 50);
}
需要注意的是,渐变坐标是全局的,即相对于当前的坐标空间。当应用于形状时,这些坐标并不是相对于形状本身的坐标。
如下示例中,四个不同位置的小矩形应用同一渐变样式时,展示出来的填充色是不同的,这也印证了渐变坐标是全局的,而并非相对于形状本身的坐标。
function drawLinear() {
var ctx = document.getElementById('canvas').getContext("2d");
var lingrad = ctx.createLinearGradient(0, 0, 100, 100);
lingrad.addColorStop(0, "red");
lingrad.addColorStop(1, "green");
ctx.fillStyle = lingrad;
ctx.fillRect(0, 0, 25, 25);
ctx.fillRect(25, 25, 25, 25);
ctx.fillRect(50, 50, 25, 25);
ctx.fillRect(75, 75, 25, 25);
}
此外,从几何角度来看 Canvas 的线性渐变,我们可以首先连接 起点(x1, y1) 和 终点(x2, y2),然后分别做一条垂直于该连线,且经过起点和终点的直线。这两条直线中间的所夹区域即为渐变区域,而起点(x1, y1) 到 终点(x2, y2) 连线的方向即为渐变方向。
确定了渐变区域和方向,那么渐变区域就会按照 addColorStop() 方法添加的色标显示渐变色,而渐变区域之外则会显示起始色标的颜色(即:起点坐标前的区域显示第一个色标颜色,终点坐标后显示最后一个色标的颜色)。
function drawLinear() {
var ctx = document.getElementById('canvas').getContext("2d");
// Create gradients
var lingrad = ctx.createLinearGradient(25, 25, 75, 75);
lingrad.addColorStop(0, "red");
lingrad.addColorStop(1, "green");
// assign gradients to fill and stroke styles
ctx.fillStyle = lingrad;
// draw shapes
ctx.fillRect(0, 0, 100, 100);
}
此外,需要注意的是,我们使用addColorStop() 方法添加色标时,第一个色标的offset不一定是 0,最后一个色标的offset也不一定 1。那么,实际真正会显示渐变色的区域是渐变区域中起始色标之间的区域。例如,我们把上述代码中第二个色标的 offset 修改为 0.5,会显示如下效果。
3.2 径向渐变
Canvas 中可以通过createRadialGradient(x1, y1, r1, x2, y2, r2) 方法创建一个径向渐变。
该方法需要指定六个参数,三个参数定义渐变的起始圆,另外三个参数定义渐变的结束圆。
x0:开始圆形的 x 轴坐标。y0:开始圆形的 y 轴坐标。r0:开始圆形的半径。必须为非负有限值。x1:结束圆形的 x 轴坐标。y1:结束圆形的 y 轴坐标。r1:结束圆形的半径。必须为非负有限值。
该方法也会返回一个 CanvasGradient 对象。我们可以调用该对象上的 addColorStop() 方法,为给定的 canvas 渐变添加一个由偏移值(offset)和颜色值(color)指定的色标。
其基本使用如下:
function draw() {
var ctx = document.getElementById('canvas').getContext("2d");
// 创建渐变
var radgrad = ctx.createRadialGradient(50, 50, 10, 50, 50, 50);
radgrad.addColorStop(0, "#ed1941");
radgrad.addColorStop(1, "#1d953f");
// 画图形
ctx.fillStyle = radgrad;
ctx.fillRect(0, 0, 100, 100);
}
如图所示,createRadialGradient会在起始圆和结束圆之间的区域创建一个径向渐变。
如上所述,Canvas 中创建一个线性渐变需要定义起始圆和终点圆的坐标和半径,从几何的角度来看,两个圆之间会存在同心、包含、内切、相交、外切、相离六种关系。当将径向渐变应用于图形绘制时,我们可以将这些关系分为三类,每类会产生不同的效果:
下列示例皆以该代码为基础
function drawItem(ctx, startCircle, endCircle) {
// 创建渐变
var radgrad = ctx.createRadialGradient(...startCircle, ...endCircle);
radgrad.addColorStop(0, "#ed1941");
radgrad.addColorStop(1, "#1d953f");
// 画图形
ctx.fillStyle = radgrad;
ctx.fillRect(0, 0, 200, 200);
ctx.strokeRect(0, 0, 200, 200)
ctx.stroke();
ctx.beginPath();
ctx.arc(...startCircle, 0, Math.PI * 2)
ctx.stroke();
ctx.beginPath();
ctx.arc(...endCircle, 0, Math.PI * 2)
ctx.stroke();
}
// startCircle: 起始圆 [x0, y0, r0]
// endCircle: 结束圆 [x1, y1, r1]
drawItem(ctx, [100, 100, 20], [100, 100, 40]);
- 同心、包含
这两种关系的共同特点就是一个圆完全位于另一个圆的内部,并且两圆没有交点。
如下图所示,这类情况下,应用图形绘制时,除了两个圆之间的部分会显示渐变色。内圆之内和外圆之外也会显示对应色标的颜色。
- 相切
这种关系下,两个圆恰好有一个交点,并且其中一个圆完全位于另一个圆的内部。
如下图所示,这类情况下会发生截断,在两个圆切线之外的部分,不会应用到该渐变样式**。
- 相交、外切、相离
这三种关系的共同特点在于,两个圆之间都会存在两条公切线。
如下图所示,这类情况下也会发生截断,在两个圆公切线之外的部分,也不会应用到该渐变样式。
综上所述,Canvas 中径向渐变应用于图形绘制时,起始圆和结束圆之间的部分会显示渐变色,而两个圆公切线之外的部分会被截断,不会应用到渐变样式。
四、线性渐变坐标与渐变角度转换
由上文可知:
- CSS 中线性渐变方向是通过 关键字或角度 来指定的,
- SVG 和Canvas 中线性渐变方向是通过 起始点坐标 来指定的
那么这两者之间如何进行转换呢?
在此之前,我们先回顾一些数学中三角函数相关的知识。如下图坐标系中,两点坐标分别为(x1, y1) 和 (x2, y2),两点连线与 x轴正方向 夹角为 α,那么它们之间会存在如下三角函数关系:
其中 tan(α) 的值也就是我们常说的直线斜率。有了这层关系,我们也就能进行渐变坐标与渐变角度之间的转换了。
注意: 下面讨论的相关转换都是以 元素边界框 为坐标系的,且起始点坐标都是以 百分比 的形式给出,而非绝对长度。
渐变坐标 -> 渐变角度
有了上述三角函数知识,我们可以简单的通过公式 计算得到起始点连线与 x轴正方向 的夹角,但是这个夹角 θ 并非我们所要的渐变角度。需要注意以下两点:
- CSS中指定的渐变角度指的是与 y轴正方向 的夹角 β,所以上述公式需修改为,这样计算得到的才是与 y轴正方向 的夹角
- SVG 中坐标系的y轴正方向是向下的,与一般坐标系是相反的,所以第一步中修改后的公式计算的是与 y轴负方向 的夹角。那么与正方向的夹角应为 ,所以通过如下公式转换,得到最终的转换公式应为:
具体代码实现如下:
const calcGradientAngle = (x1, y1, x2, y2) => {
// 计算向量
const dx = x2 - x1;
const dy = y2 - y1;
// 使用 Math.atan2 计算角度(弧度)
const thetaRadians = Math.atan2(dx, -dy);
// 将弧度转换为角度
const thetaDegrees = Math.floor(thetaRadians * (180 / Math.PI));
return thetaDegrees;
};
渐变角度 -> 渐变坐标
关于渐变角度到渐变坐标的转换大体上也是根据上述三角函数知识去转换,但是角度到坐标的转换比较灵活,这里个人细说了。下面提供一段程序,可用于将下图中的渐变角度转换成起始点坐标(x1, y1) (x2, y2)。
const calcGradientPoint = (angle) => {
const formatNumber = (num) => parseFloat(num.toFixed(2));
// 角度范围 -360 --- 360
if (angle < -360 || angle > 360) {
throw new Error();
}
// 负角度处理
const positiveAngle = angle < 0 ? 360 + angle : angle;
// 特殊处理,因为 tan(90)等值为无穷大
const anglePointMap = {
0: [[0.5, 1], [0.5, 0]],
90: [[0, 0.5], [1, 0.5]],
180: [[0.5, 0], [0.5, 1]],
270: [[1, 0.5], [0, 0.5]],
360: [[0.5, 1], [0.5, 0]]
}
if (anglePointMap[angle]) {
return anglePointMap[angle];
}
// 计算斜率
const k = formatNumber(1 / Math.tan(angle / 180 * Math.PI));
// 直线方程: y = kx - k/2 - 1/2
// 直线方程不是 y = kx 的原因在于,把坐标原点由 (0, 0) 移动到了 (-1/2, 1/2)
// y + 1/2 = k(x - 1/2) => y = kx - k/2 - 1/2
// y 坐标计算时会取负值,因为svg的坐标轴与一般坐标轴y轴方向相反
// x轴交点
const xPoint= [formatNumber(1/2 + 1/(2*k)), 0];
// y轴交点
const yPoint = [0, formatNumber(k/2 + 1/2)];
// 与直线 x = 1 的交点
const x1Point = [1, formatNumber(1/2 - k/2)];
// 与直线 y = -1 的交点
const y1Point= [formatNumber(1/2 - 1/(2*k)), 1];
if (angle <= 45 || angle > 315) {
return [y1Point, xPoint]
} else if (angle <= 135) {
return [yPoint, x1Point]
} else if (angle < 225) {
return [xPoint, y1Point]
} else if (angle < 315) {
return [x1Point, yPoint]
}
}
上述代码中一些细节这里就不过多描述了,如有错误之处,请大佬们指点。
此外,有关径向渐变,CSS 中是通过指定渐变中心、渐变形状、渐变大小等来创建的,而SVG和Canvas中是通过指定起点圆和终点圆来创建的。两者之间在下列情况下可能会表现一致,即:
- CSS: 渐变中心为,(cx, cy), 渐变形状设置为
circle,并指定了渐变大小为一个指定长度 length - SVG 或 Canvas: 起点圆为(cx, cy, 0), 终点圆 (cx, cy, length)
所以这里就不讨论径向渐变的转换了,如果有好的转换方式,求大神们指点!
本文到这里就结束了!有错误或者不清晰的地方,欢迎讨论!