从这周开始,我将会把每周看到有关于CSS有意思的技巧整合成一篇文章。将会在每周的星期天整理发布,每篇文章中将会以CSS的技巧为主线进行介绍,但每个技巧不会深入的阐述。主要目的给对CSS感兴趣的同学增强CSS的眼界,扩大知识面和使用场景。同时也希望能帮助大家将一些CSS技巧运用到实际项目中,另外提高自己在这方面的技术。如果感兴趣的话,可以持续关注,或者有你相关的技巧也可以和我们一起共享。
响应式图片
近来对于响应式设计的热度不怎么多,但事实上在Web的响应式设计中,响应式图片的处理一直都是一个难题,也是较为难处理。虽然有关于响应式图片的处理有难度,但社区一直就没有停过对其探讨。从最的简陋和最见效的方式:
img {
max-width: 100%;
height: auto;
}
这个方案虽然有效,也最为直接,但对于用户而言是最不佳的。特别是面对现在场景(终端设备众多),为了高清显示,不管三七二十一,都会加载最高清的图片。如此一来,对于高端设备而言没有太大问题,但对于低端设备而言就过于浪费了。除了加载慢还浪费客户的带宽。
而对于Web而言,图片对用户又非常有吸引力,那么我们在使用图片的时候就必须考虑:
- 图片格式
- 图片大小(容器)
- 渲染尺寸(浏览器中布局的宽度和高度)
- 图片尺寸(图片原宽高)
- 纵横比(宽高比)
对于开发者而言,如何正确的选择这些或者更好的混合使用,为用户提供最佳的体验。要知道答案其实也并不难,可以尝试着下面这些问题的答案来进行选择:
- 图像是动态的(用户创建)还是静态的(设计团队创建)的?
- 图像的宽高比不成比例变化会影响图片质量吗?
- 是否所有图像都在相同的宽度和高度中渲染?渲染时,它们必须有一个特定的长宽比还是一个完全不同的长宽比?
- 在不同的视窗中显示图像时需要考虑什么?
具体操作时,把这些问题的答案记录下来,这样不仅可以助于你理解图像,还能有助于你做出正确的选择。
对于图像来源(动态还是静态)很好确定,而其中静态来源更好的处理,但如果图像资源是动态的,相对而言是较为复杂的。下面有一个例子值得我们研究分析,来确定最重要的设备和视窗大小。
在@Jo Franchetti的博文《Common Responsive Layouts with CSS Grid》中介绍了如何通过grid-template-columns
来让布局具有适应性(响应式),也就是说借助该技巧,再配合max-width:100%
,我们就可以:
#app {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
grid-gap: 10px;
img {
max-width: 100%
}
}
虽然上面的示例让我们可以不借助媒体查询实现响应式布局,但我们依旧还是使用一张图片来匹配不同终端,始终还是有一定的伪合度:
而我们真正想要的,或者更佳的方案是:
根据设置的不同,提供不同的图片资源;或者根据屏幕高清度(
dpr
)不同,提供不同的图片资源。一句话就是,给你的用户提供正确的图像!
实现上面的需求,可以通过下面的方案来达到我们的诉求:
- 使用
<img>
元素加载图像,使用该元素最新属性srcset
和size
给用户加载正确的图像 - 使用
<picture>
元素给用户加载正确的图像
比如<img />
:
再为看<picture>
:
扩展阅读
纵横比aspect-ratio
CSS有一个常见的问题无法根据长宽比来调整大小。特别是在处理响应式设计时,通常希望能够将宽度设置为百分比,并使用高度对应于某个纵横比。针对该问题,也是负责设计CSS的人员(即CSS工作组)一直在讨论的问题,最近在旧金山举行的CSSWG会议上针对这方面的解决方案的提议得到了一致性的通过。
在该提议之前,Web开发人员一直以各种方式处理纵横比,其中最主要的方案是借助padding
(padding-top
或padding-bottom
)设置一个百分比值。原理非常的简单,padding
的百分比是根据元素的width
来计算的。如此一来就可以达成到我们想要的效果(模拟出纵横比效果)。除了这种方案,CSS中还有其他的一些技巧可以实现长宽比。有关于这方面的介绍,在互联网上也较多,比如:
虽然上述提到的方案可以实现纵横比,但大家还在寻找一个通用的解决方案。W3C的CSS Sizing 4 specification规范中就提供了这样的通用解决方案。规范中提供了aspect-ratio
属性。该属性将接受一个长宽比的值,比如16/9
。如果想要一个宽度和高度相同的盒子,可以像下面这样使用:
.box {
width: 400px;
height: auto;
aspect-ratio: 1/1;
}
如果希望是一个16/9
的盒子,只需要把aspect-ratio
的值设置为16/9
:
.box {
width: 100%;
height: auto;
aspect-ratio: 16/9;
}
另外,该属性要是结合在CSS的Grid布局中的话,可以轻易地实现每个元素根据容器宽度来调整自身的高度,比如:
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
.item {
aspect-ratio: 1/1;
}
aspect-ratio
属性能被纳入W3C的规范,我们都需要感谢@Jen Simmons的努力付出,她是第一位提出该方案的,有关于详细的介绍,可以阅读这份PPT。
有关于这方面更详细的介绍也可以阅读@Rachel Andrew 的博文《Designing An Aspect Ratio Unit For CSS》。
嵌套选择器
CSS选择器虽然有很多种,但一直以来都没有嵌套选择器。但在CSS处理器中这已经是非常成熟的一种技术了。不过本月初,CSS嵌套选择器发被定义为CSS嵌套模块,而且发布了第一个草案。该草案概述了一种未来的机制,通过这种机制,我们将来能够在本地嵌套CSS选择器。
比如下面这样的示例:
.foo {
color: blue;
& > .bar {
color: red;
}
}
/* 等效于 */
.foo {
color: blue;
}
.foo > .bar {
color: red;
}
.foo {
color: blue;
&.bar {
color: red;
}
}
/* 等效于 */
.foo {
color: blue;
}
.foo.bar {
color: red;
}
.foo, .bar {
color: blue;
& + .baz, &.qux {
color: red;
}
}
/* 等效于 */
.foo, .bar {
color: blue;
}
:matches(.foo, .bar) + .baz,
:matches(.foo, .bar).qux {
color: red;
}
使用嵌套选择器时,&
符不可缺,不然是无效的,比如:
.foo {
color: red;
.bar {
color: blue;
}
}
另外还可以使用 @nest
规则:
.foo {
color: red;
@nest & > .bar {
color: blue;
}
}
/* 等效于 */
.foo {
color: red;
}
.foo > .bar {
color: blue;
}
.foo {
color: red;
@nest .parent & {
color: blue;
}
}
/* 等效于 */
.foo {
color: red;
}
.parent .foo {
color: blue;
}
.foo {
color: red;
@nest :not(&) {
color: blue;
}
}
/* 等效于 */
.foo {
color: red;
}
:not(.foo) {
color: blue;
}
使用@nest
规则时也需要注意,不然也会不生效,比如下面这两个示例代码就是不会生效的选择器:
.foo {
color: red;
@nest .bar {
color: blue;
}
}
.foo {
color: red;
@nest & .bar, .baz {
color: blue;
}
}
这是原生的嵌套选择器,不需要借助于任何CSS处理器。注意,该选择器更类似于Sass的嵌套。
扩展阅读
动态生成内容
CSS的世界当中,要生成内容都是依赖于content
属性来完成,一般配合伪元素::before
和::after
来完成。除了在content
中显式的设置要显示的内容之外,还可以借助attr()
属性来获取HTML标签元素上的属性的值。今天要给大家介绍的是另一种方式。借助于CSS计数器的属性counter-reset
再配合CSS自定义属性,我们可以做更多的事情。比如下面这个示例,可以在Icon添加一个数字:
<div class="icon">
<svg width="100%" viewBox="0 0 24 24">
<path d="M4,4H20A2,2 0 0,1 22,6V18A2,2 0 0,1 20,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4M12,11L20,6H4L12,11M4,18H20V8.37L12,13.36L4,8.37V18Z"></path>
</svg>
</div>
.icon {
--number-var: 23;
}
.icon::before {
counter-reset: number var(--number-var);
content: counter(number);
}
结果如下:
要是借助CSS自定义属性的API,我们可以动态的改变数值:
let styles = getComputedStyle(document.querySelector('.icon'));
let increaseValue = Number(styles.getPropertyValue('--number-var')) + 1;
let decreaseValue = Number(styles.getPropertyValue('--number-var')) - 1;
document.getElementById('increase').addEventListener('click', function(e){
document.querySelector('.icon').style.setProperty('--number-var', increaseValue++)
})
document.getElementById('decrease').addEventListener('click', function(e){
document.querySelector('.icon').style.setProperty('--number-var', decreaseValue--)
})
你点击+
号或-
号按钮都会改变图标上的数字:
扩展阅读
下拉菜单
怎么做下拉菜单对于前端的同学而言已不是什么新事物了,但今天向大家介绍另类的方式,那就是借助HTML5的<details>
和<summary>
元素来做一个可点击的下拉菜单。比如下面这个示例:
其实使用<details>
和<summary>
元素除了可以做下拉菜单之外还可以做手风琴和对话框。
扩展阅读
- 借助HTML5
details
,summary
无JS实现各种交互效果 - Javascript-free dropdown menus
- Using
<details>
for Menus and Dialogs is an Interesting Idea
滚动的另类使用
在《改变用户体验的滚动新特性》和《滚动的特性》两篇文章中介绍了滚动条的一些特性以及带来的用户体验。这些属性对于当今来说都是一些新特性,有些浏览器都还不支持。但这些特性真的很有意思。比如下面这样的一个Demo效果:
当你滚动页面的时候,可以看到卡通人物不断的在更换衣物:
如果不看代码,估计很多同学都会以为这是JavaScript完成的,事实上是纯CSS完成的,而且关键代码就两行:
.main-content {
scroll-snap-type: y mandatory;
overflow-y: scroll;
}
section {
scroll-snap-align: center;
}
为了给用户最好的体验,都追求流畅,比如滚动效果,使用scroll-behavior: smooth
让滚动流畅。除了使用CSS之外,还可以使用JavaScript来达到相应的效果:
window.scrollTo({
top: document.body.getBoundingClientRect().height,
behavior: 'smooth'
});
但上面的方法,我们无法控制滚动的速度。@Jedidiah Hurt发现了一个小技巧,可以改变这一切。借助CSS的transform
来帮助我们完成:
const scrollElement = (element, scrollPosition, duration) => {
// useful while testing to re-run it a bunch.
// element.removeAttribute("style");
const style = element.style;
style.transition = duration + 's';
style.transitionTimingFunction = 'ease-in-out';
style.transform = 'translate3d(0, ' + -scrollPosition + 'px, 0)';
}
scrollElement(
document.body, (
document.body.getBoundingClientRect().height - document.documentElement.clientHeight + 25
),
5
);
JS in CSS
JS in CSS对于很多同学来说是新东西。或许很多同学会说,应该是CSS in JS吧。事实上真是JS in CSS。也就是说,以后在CSS有世界中,不仅仅是写CSS了,可以在CSS中写JS。是不是很神奇。这样一来,我们可以轻易的扩展CSS。当然,有接触过CSS Paint的同学立马就会想到,应该是采用这种技术吧。事实上是这样的,不同的是,在CSS的代码中可以写registerPaint
相关的paint
函数。所以看起来是JS in CSS。
在CSS代码中直接编写registerPaint
的paint
函数时,可以接受:
ctx
,2D渲染上下文geom
,元素的几何形状
甚至还可以编写自己的CSS自定义属性,并在JavaScript中使用。
<!-- CSS -->
.el {
--color: cyan;
--multiplier: 0.24;
--pad: 30;
--slant: 20;
--background-canvas: (ctx, geom) => {
let multiplier = var(--multiplier);
let c = `var(--color)`;
let pad = var(--pad);
let slant = var(--slant);
ctx.moveTo(0, 0);
ctx.lineTo(pad + (geom.width - slant - pad) * multiplier, 0);
ctx.lineTo(pad + (geom.width - slant - pad) * multiplier + slant, geom.height);
ctx.lineTo(0, geom.height);
ctx.fillStyle = c;
ctx.fill();
};
background: paint(background-canvas);
transition: --multiplier .4s;
}
.el:hover {
--multiplier: 1;
}
<!-- JS: registerPaint module -->
registerPaint('background-canvas', class {
static get inputProperties() {
return ['--background-canvas'];
}
paint(ctx, geom, properties) {
eval(properties.get('--background-canvas').toString())(ctx, geom, properties);
}
})
效果如下:
@Una Kravets在Coepen上写了一个更复杂的示例:
有关于这方面更多的示例还可以点击这里查看,对于的代码可以在Github上查看。
如此一来,我们可以借助JavaScript的能力,发挥自己的创意,在CSS中做更多有意思的事情。如果你在这方面有较好的创意,欢迎在下面的评论中与我们一起分享。
扩展阅读
Flexbox的计算工具
Flexbox布局已经是非常成熟的布局,但对于flex-grow
、flex-shrink
和flex-basis
三个属性的演算(算法)的理解还是较为复杂的。推荐一个在线工具Flexulator给大家,帮助大家更好的理解这三个属性以及他们之间的演算。
通过上面的工具,再结合《聊聊Flexbox布局中的flex的演算法》一文,你会更彻底的了解flex-grow
、flex-shrink
和flex-basis
属性。有关于Flexbox布局更多的教程,可以点击这里阅读。
小结
今天介绍了一些小技巧,主要涉及到响应式图片的处理、纵横比属性aspect-ratio
、嵌套选择器、自定义属性配合计数器动态生成内容、借助HTML5的<details>
和<summary>
元素来做一个可点击的下拉菜单,滚动特性以及JS-in-CSS。其中aspect-ratio
、 嵌套选择器和Js-in-CSS是较新的特性,特别是后两者,应该是最有为意思的部分。
最后希望这些小技巧能帮助大家增长见识,也希望大家能喜欢。如果你有更好的建议或相关的经验欢迎大家在下面的评论中与我们一起分享。特别推荐,大家要是有时间可以尝试使用JS-in-CSS,发挥您的创意,制作一些有意思的作品。