世界杯正在进行中,也不要忘记学习 CSS(得想办法蹭一波热度)。比如,用 CSS 绘制一个足球场?
一眼望去,这里的形状只有圆形和矩形,在不借助其他标签的情况下(包括伪元素),其实很容易联想到渐变,一起看看如何绘制的吧,有非常多的渐变小技巧~
温馨提示:文章中带有“⚽️”的描述属于足球小知识,不感兴趣的可以跳过,与 CSS 无关
一、场地的尺寸设计
首先来看一下足球场的构造,下面是在网上找到的一张设计图
主要有以下几个部分
- 整体是矩形,边线和底线,长度是
109~131码(90~120m)
,宽度是49-98码(45~90m)
- 中线,贯穿球场
- 开球点,位于中线的中心
- 中圈,以开球点为圆形,半径为
10码(9.15m)
圆 - 罚球区(大禁区),长度是
44码(40.3m)
,宽度是18码(16.5m)
- 球门区(小禁区),长度是
20码(18.3m)
,宽度是6码(5.5m)
- 球门,长度是
8码(7.32m)
- 罚球点(点球点),距离球门
12码(11m)
- 罚球弧(禁区弧),以罚球点为圆心,半径为
10码(9.15m)
的圆弧 - 角球区,以4个角为圆心,半径为
1m
的 1/4 圆弧
⚽️现代足球运动起源于英国,当时英制的长度单位是“码”,1码等于0.9144米,约0.915米。 那么,英制长度10码换算成公制长度就是9.15米。 这就是9.15米的来源。
二、边线和底线
以世界杯比赛场地为例,长度115码(105m)
,宽度74码(68m)
。
⚽️2022世界杯决赛将在卢赛尔球场举行,它由中国铁建国际集团承建
假设 HTML
结构如下,这里用到的标签仅仅只有一个
为了尺寸计算方便,采用em
相对单位进行换算,数值就可以采用真实的“码”,好处是都是整数,简单绘制一下外围边线和底线,如下
filed{
font-size: 5px;
width: 115em; /*表示115码*/
height: 74em;
border: 5em solid transparent;
outline: 2px solid #fff;/*线宽就用固定值*/
outline-offset: -5em;
background: #43A63C;
}
注意这里实现的小细节:这里外层的线框是通过outline
实现的,还预留了一个5em
的透明边框,这是因为背景的位置和尺寸百分比计算是根据内容区域的,并不包含边框。比如,background-postion:0 0
表示的就是线框内的左上点,background-postion: 100% 100%
表示的就是线框内的右下点,,background-size: 100%
表示的就是线框内最大尺寸,如下
还有一个好处,透明边框仍然是可以绘制背景的,比如深浅不同的草皮就可以绘制在边框之外,这个后面再说。
准备工作完成了,下面是具体的绘制过程
三、中线、开球点和中圈
在绘制之前,可以简单规划一下,通过 CSS 变量将各部分分离开来,这样看起来会更加清晰,例如
filed{
--中线: xxx;
--中圈开球点: xxx;
...
background:
/*越靠前的背景越靠上*/
var(--中线),
var(--中圈开球点),
...,
#43A63C
}
这个技巧在之前这篇文章中有相关介绍:由 transform 被占用引发的思考
首先是中线,使用线性渐变linear-gradient
--中线: linear-gradient(#fff, #fff) center/2px 100% no-repeat;
可能有些同学还不太清楚上面这一段 background
的简写,可以看下面这张图(下面只列举了常用的,实际上还有更多属性)
效果如下
然后是开球点和中圈
⚽️球在开出前,其他队员不得进入中圈。
这是一个半径为10码
的圆,可以通过一个径向渐变radial-gradient
实现,
--中圈: radial-gradient( closest-side circle, #fff 2px, transparent 0 calc(100% - 2px), #fff 0 100%, transparent 0) center/20em 20em no-repeat;
注意,这里使用了关键词closest-side
,表示最近的边,可以根据背景尺寸直接控制圆的大小,默认值是farthest-side
,其他选项详细如下
关键字 | 描述 |
---|---|
closest-side |
渐变中心距离容器最近的边作为终止位置。 |
closest-corner |
渐变中心距离容器最近的角作为终止位置。 |
farthest-side |
渐变中心距离容器最远的边作为终止位置。 |
farthest-corner(默认值) |
渐变中心距离容器最远的角作为终止位置。 |
当然,对于完全对称的容器,closest-*
和 farthest-*
是完全相同的,各自的区别如下所示
绘制效果如下
四、罚球区、球门区
罚球区通常也叫大禁区,是一个44码*18码
的矩形线框。
⚽️守门员在罚球区内可以用手触球,出去就不行了,还有就是在这个区域犯规,容易被判点球
线性渐变不像径向渐变那样,并不能直接画出这种矩形线框,我采用的方式是覆盖的方式,也就是一块绿色的覆盖在一块白色的背景之上,实现如下
--大禁区: linear-gradient(#43A63C, #43A63C) left center/calc(18em - 2px) calc(44em - 4px) no-repeat, linear-gradient(#fff, #fff) left center/18em 44em no-repeat;
动态演示如下
两边半场都有罚球区域,而且长的也完全一样,需要再重新绘制一份一模一样的吗?
☝️☝️☝️当然不需要!
凡事看到相同或相似的图案都需要考虑能否避免重复的工作量?
仔细观察,两边的罚球区域正好可以通过水平平铺repeat-x
实现,只需要改变背景大小就行了,示意如下:
你也可以查看动态演示:
用代码实现就是
--大禁区: linear-gradient(to right, #43A63C calc(18em - 2px), transparent 0) 0 center/calc(100% - 18em + 2px) calc(44em - 4px) repeat-x, linear-gradient(to right, #fff 18em, transparent 0) 0 center/calc(100% - 18em) 44em repeat-x;
这样就能在不增加渐变数量的情况下实现多份相同的背景,效果如下
然后是球门区,通常叫小禁区,是一个20码*6码
的矩形线框。
⚽️球门球时,由守方队员在球门区内任意一点将球放定后踢出
这个和罚球区绘制方法一致,就不多描述了
--小禁区: linear-gradient(to right, #43A63C calc(6em - 2px), transparent 0) 0 center/calc(100% - 6em + 2px) calc(20em - 4px) repeat-x, linear-gradient(to right, #fff 6em, transparent 0) 0 center/calc(100% - 6em) 20em repeat-x;
效果如下
最后是球门。球门在底线后面,由于是俯视图,只需要知道长度8码
就行了,宽度可以随便给一个
⚽️足球要完全越过门线才算进球,仅超过1/2也不行
这个和上面绘制方法一致,不过需要注意background-position
的位置,是负数
--球门: linear-gradient(to right, #43A63C calc(3em - 4px), transparent 0) calc(-3em + 2px) center/calc(100% + 3em) calc(8em - 4px) repeat-x, linear-gradient(to right, #fff 3em, transparent 0) -3em center/calc(100% + 3em) 8em repeat-x;
这里就体现出前面透明的好处了,可以将背景绘制在边框上,效果如下
这样就完成了所有矩形线框的绘制
五、罚球点、罚球弧、角球弧
最后是几条比较重要的圆弧。
首先是罚球点,又称点球点,距离球门12码
⚽️这个相信很多人都知道,12码点球主罚的地方就是这里
这是一个圆点,直接通过一个径向渐变radial-gradient
绘制,这个我们等会再来绘制,先画下面的
然后是罚球弧,它是以罚球点为圆心,半径为10码
的圆弧,和中圈一样
⚽️在罚点球时,除守方守门员和主罚队员外,双方其他队员都必须退出罚球区及罚球弧外,因为到罚球点的距离都是 10 码
和中圈大小是一样的,只是位置不同,注意圆心距离左侧的距离是12码
,由于左右都是一样的,可以用前面提到的repeat-x
方式实现(当然这里不写也行,因为默认就是平铺的),示意如下:
你也可以查看动态演示:
用代码实现就是
--罚球弧: radial-gradient( circle at 12em center, transparent calc(10em - 2px), #fff 0 10em, transparent 0) 0 center/calc(100% - 24em) 100%;
效果如下
现在这个圆弧覆盖在最上面,由于罚球区是通过覆盖的方式实现的,所以可以将罚球弧放在罚球区下面,通过改变background
的先后顺序就可以了,如下
多背景的情况下,前面的背景层级 > 后面的背景层级
background:
/* var(--罚球弧), */
var(--小禁区),
var(--大禁区),
var(--罚球弧), /* 将罚球弧移动到大禁区下面*/
var(--球门),
var(--中线),
var(--中圈),
#43A63C;
}
这样就正常了
然后用同样的方式把罚球点补上
--罚球点: radial-gradient( circle at 12em center, #fff 2px, transparent 0) 0 center/calc(100% - 24em) 100%;
效果如下
最后是角球弧。位于球场四角,是半径为1m
的 1/4 圆弧。
⚽️用来发角球的地方,守方队员在己方半场将球自底线处踢出界外,攻方获得的就是发角球的机会
如果按照一般的思路,肯定是绘制 4 个 1/4 圆弧就行了,不过这里还可以采用另一种方式。仔细想一下,这4个圆弧合在一起是不是刚好就是一个完整的圆?经过位移、平铺就可以了,思路演示如下
这个思路在之前这篇文章中有用到:CSS 实现优惠券的技巧
用代码实现就是
--角球弧: radial-gradient( circle at 1em 1em, transparent calc(1em - 2px), #fff 0 1em, transparent 0) -1em -1em/100% 100%;
这样用一个渐变就同时绘制出了4个圆,效果如下
可以看到,4个圆弧已经均匀分布在4个角落了,但是已经超出了线外,这个也非常好解决,默认情况下背景是可以绘制在border-box
范围内的,只需要通过background-clip
裁剪到content-box
内就行了,如下
--角球弧: radial-gradient( circle at 1em 1em, transparent calc(1em - 2px), #fff 0 1em, transparent 0) -1em -1em/100% 100% content-box;/*添加裁剪区域*/
这样就完美了
六、草坪
到目前位置,球场的所有部分基本已经完成了,最后再铺上深浅相间的草坪
⚽️不仅仅是为了好看,还能缓解观众和球员的视觉疲劳,同时也方便裁判和球员看清是否越位
关于草坪的条纹间隔貌似不是很统一?这里就大概给一个数值,用线性渐变即可,如下
--草坪: linear-gradient(90deg, transparent 50%, rgba(0,0,0,.4) 0% 100%) center/10% 100%;
由于前面罚球区等用到了覆盖的方式,所以这里不能直接将草坪绘制在最底下,需要绘制在最上层,效果如下
可以看到,仅仅通过半透明的覆盖还是很不自然的,所以需要将颜色混合一下,需要用到background-blend-mode
,比较复杂,这里就不展开了,设置如下
overlay
,soft-light
,hard-light
属于融合混合模式,比较适合这类情况
background-blend-mode: soft-light /*柔光*/
效果如下
有点奇怪...原因是上面设置的是所有图层的background-blend-mode
,我们其实只需要设置最顶层的草坪,那怎么处理呢?我起初以为background
缩写是包含background-blend-mode
的,这样的话可以单独给某一层设置了,可惜这个不支持(我猜测这个属性出现的稍晚一些,没有统一到背景里)。
没办法,只能逐层设置了,第1层设置 soft-light
,剩下的全部都是normal
,如下
background-blend-mode: soft-light, normal,...,normal;
后面的normal
可以多,但是不能少,原因在于,会根据背景数量自动补充,如果数量不足,会重新开头循环(这个补充规则其实跟其他背景属性,例如 background-size
是一样的)
这样就非常自然了,效果如下
下面是完整代码(其实没多少行):
filed{
font-size: 5px;
width: 115em;
height: 74em;
--中线: linear-gradient(#fff, #fff) center/2px 100% no-repeat;
--中圈: radial-gradient( closest-side circle, #fff 2px, transparent 0 calc(100% - 2px), #fff 0 100%, transparent 0) center/20em 20em no-repeat;
--大禁区: linear-gradient(to right, #43A63C calc(18em - 2px), transparent 0) 0 center/calc(100% - 18em + 2px) calc(44em - 4px) repeat-x, linear-gradient(to right, #fff 18em, transparent 0) 0 center/calc(100% - 18em) 44em repeat-x;
--小禁区: linear-gradient(to right, #43A63C calc(6em - 2px), transparent 0) 0 center/calc(100% - 6em + 2px) calc(20em - 4px) repeat-x, linear-gradient(to right, #fff 6em, transparent 0) 0 center/calc(100% - 6em) 20em repeat-x;
--球门: linear-gradient(to right, #43A63C calc(3em - 4px), transparent 0) calc(-3em + 2px) center/calc(100% + 3em) calc(8em - 4px) repeat-x, linear-gradient(to right, #fff 3em, transparent 0) -3em center/calc(100% + 3em) 8em repeat-x;
--罚球弧: radial-gradient( circle at 12em center, transparent calc(10em - 2px), #fff 0 10em, transparent 0) 0 center/calc(100% - 24em) 100%;
--罚球点: radial-gradient( circle at 12em center, #fff 2px, transparent 0) 0 center/calc(100% - 24em) 100%;
--角球弧: radial-gradient( circle at 1em 1em, transparent calc(1em - 2px), #fff 0 1em, transparent 0) -1em -1em/100% 100% content-box;
--草坪: linear-gradient(90deg, transparent 50%, rgba(0,0,0,.5) 0% 100%) center/10% 100%;
background:
var(--草坪),
var(--角球弧),
var(--罚球点),
var(--球门),
var(--小禁区),
var(--大禁区),
var(--罚球弧),
var(--中线),
var(--中圈),
#43A63C;
background-blend-mode: soft-light, normal, normal, normal, normal, normal, normal, normal, normal, normal, normal, normal;
border: 5em solid transparent;
outline: 2px solid #fff;
outline-offset: -5em;
}
你也可以访问以下任意链接:
- CSS football filed - 码上掘金 (juejin.cn)
- CSS football filed (codepen.io)
- CSS football filed (runjs.work)
七、总结一下
渐变是 CSS 中非常强大但比较繁琐的功能了,几乎是万能的,能够设计出来的都可以通过渐变绘制出来,只是实现的复杂度问题,擅于发现图形的规律也能有效地减少渐变的复杂度,下面简单总结一下
- 背景的位置和尺寸的百分比计算是根据内容区域的,并不包含边框
- 透明边框仍然是可以绘制背景的
- 通过 CSS 变量将背景各部分分离开来,这样看起来会更加清晰
- 线性渐变不像径向渐变那样,并不能直接画出这种矩形线框,可以用两层渐变覆盖的方式实现
- 凡事看到相同或相似的图案都可以考虑使用背景平铺来减少工作量
- 多背景的情况下,前面的背景层级 > 后面的背景层级
- 4 个 1/4圆可以通过一个完整的圆位移平铺实现
- 默认情况下背景是可以绘制在边框范围内的,可以通过
background-clip
改变绘制范围 - 背景混合模式并不在
background
简写属性中,需要单独书写,可以让图层混合更加自然 - 背景混合模式会根据背景数量自动补充,如果数量不足,会重新开头循环
当然实际工作中肯定是不推荐的,更多是学习掌握渐变相关知识,这样绘制一些活动小图标,或者在某些不方便改变 HTML 结构时可以有更多的思路和方案,不是吗?最后,如果觉得还不错,对你有帮助的话,欢迎点赞、收藏、转发❤❤❤