译者:cherryvenus
纯CSS过滤内容
译者提示:
仅仅通过CSS过滤内容,这是一个非常有趣且有吸引力的方法。 (译者注:作者在这里是说,他在半夜两点的时候喝了酒,然后突然想做个实践,就是纯CSS过滤内容)
通过CSS的:target的伪选择器,用户点击导航之后,我们可以过滤出所需内容。通过用相邻的兄弟选择器,我们可以定义DOM后续部分的样式。
来看看一个例子demo.
HTML
我们从简单地定义需要过滤内容开始,.c-filter。所有的元素都会放在section这个节点中。
<section class=""c-filter"">
...
</section>
接着,我们罗列出过滤目标。每个过滤主题都有一个目标,并且每个元素上都加上了相同的class,并且配有一个和主题相关的特殊ID。这些ID是:target选择器之后会查找的:这些是准备工作整个技术的核心框架。
他们都是被定义为(空)锚元素<a>,因为他们就是他们的样子:用来链接的锚。
<a class=""c-filter__target"" id=""tag:css""></a>
<a class=""c-filter__target"" id=""tag:html""></a>
<a class=""c-filter__target"" id=""tag:accessibility""></a>
<a class=""c-filter__target"" id=""tag:performance""></a>
<a class=""c-filter__target"" id=""tag:vim""></a>
这之后,我们过滤用户会点击的链接:
<nav class=""c-filter__links"">
<a href=""#tag:all"" class=""c-filter__link c-filter__link--reset"">Reset</a>
<a href=""#tag:html"" class=""c-filter__link"">HTML</a>
<a href=""#tag:css"" class=""c-filter__link"">CSS</a>
<a href=""#tag:accessibility"" class=""c-filter__link"">Accessibility</a>
<a href=""#tag:performance"" class=""c-filter__link"">Performance</a>
<a href=""#tag:vim"" class=""c-filter__link"">Vim</a>
</nav>
每一个链接都有一个相同的class,并且href指向各自的锚。当一个用户点击其中一个链接,我们的:target选择器会截获信息并作出相应行为。
下面,我们想要过滤的内容。在这个例子中,这些项目是博客文章。
<article class=""c-article c-filter__item"" data-tag=""vim"">
<h2>Using Macros</h2>
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada
fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies
eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper.
Aenean ultricies mi vitae est. Mauris placerat eleifend leo.</p>
</article>
article元素有十分值得一提的元素。首先,他们都有c-article这个class,这和过滤分离没有关系的class:这个class只是为articles写样式的并且不用于过滤。
其次, 他们都有c-filter__item这个class:这只是简单的布了下局,并不是真正的过滤。
最后我们看到data-tags这个属性,这个是包含了一个或多个与过滤内容相关的标签。这些数据属性对过滤来说是必要的存在,因为我们需要通过这些值来确定哪些是用展现给户。
流程就像下面这样:
- 页面加载显示全部项目。
- 用户点击一个过滤的链接,这个链接跳至相对应的
#tags:*锚的这里,同时在url后面加上这个id。 - 我们会抓住所有的用
:target为元素的片段标识符。 - 我们会用这个知识点来显示/隐藏相关内容。
CSS
主要工作都在CSS。第一部分并没有令人兴奋的点。只是一个加点样式然后布局元素。
/**
* 1\. Clearfix our filter context.
*/
.c-filter {
overflow: hidden; /* [1] */
}
.c-filter__links {
padding: 1em;
margin-bottom: 1em;
background-color: #fff;
border-bottom: 1px solid #e5e5e5;
}
.c-filter__link {
display: inline-block;
padding: 0.5em 1em;
border-radius: 100px;
background-color: rgba(0, 0, 0, 0);
transition: 0.333s;
}
.c-filter__link--reset {
border-right: 1px solid;
border-radius: 0;
}
.c-filter__item {
float: left;
width: calc(33.333333333% - 1em);
margin-right: 0.5em;
margin-bottom: 1em;
margin-left: 0.5em;
transition: 0.333s;
}
现在,事情变得有些有趣了:
.c-filter__target:not(:target) ~ .c-filter__item {
opacity: 1;
}
这个选择器做了一些事情。让我们来分析分析他:
.c-filter__target指向我们所有空元素a。:not(:target)检查没有锚(<a>)当前是被指定的。~是相邻的兄弟选择器,并且这是寻找DOM之后出现的东西,但是却是在DOM树中相同层级的元素。.c-filter__item只是寻找我们所有可过滤的元素。
因此基本上,如果我们当前没有指定任何锚,在DOM之后的节点寻找过滤的项目并且确保他们都是可见的。
‘可见’在这里的就是 opacity: 1;。这里也可以是其他的(e.g. 在我的第一版,这里这实际上从 display: block;开始)。
接下来的一些CSS看上去有些相似:
.c-filter__target:target ~ .c-filter__item {
opacity: 0.25;
}
我们在做和之前几乎一样的查找,这次我们只是少了:not()选择器。这就意味着我们在检查哪些锚是被指向的,如果是这样的,隐藏我们所有的过滤项目 (opacity: 0.25;)。
因此这里有两件事:如果没有锚被指向,显示所有的;如果有任意的锚被指向,隐藏所有的。
当我们把所有都隐藏之后,我们想打开指定的项目。我们需要多做一些事,因为我们在寻找特定的DOM节点:我们不能用一个通用的规则。
在这里我需要用Sass帮我完成,就像这样:
@each $tag in html, css, accessibility, performance, vim {
[id=""tag:#{$tag}""]:target ~ [data-tag~=""#{$tag}""] {
opacity: 1;
}
...
}
这里,遍历每一个标签,并且遍历所有像下面这样的选择器:
`[id=""tag:css""]:target ~ [data-tag~=""css""] { ... }`
在这里发生了什么?
-
[id=""tag:css""]:target检查id和tag:css完全相同的锚是否被指定。**N.B.**我不能将此选择器写成#tag\:css:target,有两个我不能这么做的原因:首先,正如你所见我们要避开那个冒号。其次,我们在CSS中用ID。将此作为属性选择器写意味着我不需要放任何的其他特定CSS hook在hmtl中个,并且额外的好处是用class的特殊性来选择id。 -
~就像之前那样,是选择当前DOM之后的节点并且是DOM树上同一层级上的某一元素。 -
[data-tag~=""css""]是之后的元素,和DOM树在同一层级上的,~正在寻找的元素。我现在要做的就是用子选择器来找出data-tags这个属性的内容。~=明确地表示了这个属性包含了一系列空格隔开的值,然后那些值和css(在那种情况下)是一样的。
因此如果我们瞄准了一个css锚,请找所有在DOM树之后出现的标记了css的元素。一旦找了到,就将他们展示出来:
`opacity: 1;`
这就是对于过滤工作必要的事情——实际上是代码的一小部分。
突出当前过滤的链接
我认为有一个极佳的尝试,就是给当前过滤的链接加一个样式,因此用户可以看到那些内容他们想看的。这比我预期的要复杂一些,但是并不是不可解决的。
在同样的Sass循环之前,我生成了一套新的选择器,看上去像这样:
`[id=""tag:css""]:target ~ .c-filter__links [href=""#tag:css""] { ... }`
一步步通过吧:
[id=""tag:css""]:target严查了css锚当前是否被选中。~查看当前DOM后续的元素..c-filter__links就是我们在寻找的东西。[href=""#tag:css""]正在寻找href的值和#tag:css相同的元素。
在这里我发现一件很有趣的事情就是获取链接。因为他们都在nav元素(.c-filter__links)之中——因此不是在DOM树的统一层次中。根据~选择器要求那样——我不能直接通过~选择器指定他们。因为这悲伤的事实,我几乎放弃了高亮练习,但是之后我发现了解决方案。
不同于指向连接自己,我可以指定他们的nav元素因为它在DOM树中统一层级的。接下来的步奏就是寻找正确连接。这就是为什么我要嵌套最后一个选择器。
尴尬的页面跳转
还有一个我不费吹灰之力解决的问题。因为我们要链接到片段标识,页面如果很长需要滚动,那么跳转的时候就会很可怕。解决方案是就是固定一页(也就是不要滚动)。
因为我们要链接到空的锚元素,他们完全不会在页面上显示(但是他们确实在DOM中存在,这就是为什么我们让页面跳转)。通过简单的的 position: fixed;给所有的锚元素,他们会在页面左上角定位。实际的结果是锚会在用户滚动到的地方,因此页面并不会真的跳到其他地方。
.c-filter__target {
position: fixed;
}
问题解决啦~
缺点
这个技术中有一些缺点。没有特定的顺序:
- 添加新的标签意味着编写和重新编译新的CSS。我们可通过CMS将一个“样式”块注入到页面中,并且重复我们的标签,以此来绕过。因此这不完全是世界末日。
- 过滤新增的历史状态。通过过滤器点击按钮10几次可能会让你变得非常恼火。 然而,这确实意味着我们可以直接链接到过滤之后的结果。
- 可获得性。说真的,我不确定这对可获得性是好消息还是坏消息。不过毫无疑问的是我们可以做一些提升。至少它不是用
checkbox的hack,对吧? - 我能完全这么做吗?我感觉css做了js的事情。
- 讨厌的CSS,嵌套的选择器,子选择器,严重依赖位置。这不是最好的,不是吗?
自从我像这样玩转CSS,已经过了很长时间了。并且依然很有意思。我需要确认我要经常像这样做事。