假设我们想要以下 HTML 结构
<div class='boo'>
<div class='boo'>
<div class='boo'>
<div class='boo'>
<div class='boo'></div>
</div>
</div>
</div>
</div>
手动编写这真的太痛苦了。而这篇文章诞生的原因是,我看到它用 Haml 这样生成后感到恐惧
.boo
.boo
.boo
.boo
.boo
我看到的代码实际上有大约二十层嵌套,但也许有些人用手机阅读东西,所以我们不要用 boos 填充整个视窗,即使万圣节快到了。
正如您可能已经知道的那样,手动写出每一层远非理想,尤其是在 HTML 由预处理器(或来自 JavaScript,甚至来自 PHP 等后端语言)生成时。我个人不喜欢深度嵌套,我自己也使用得不多,但如果您无论如何都要这样做,那么我认为值得以一种扩展良好且易于维护的方式进行。
所以,让我们首先看看这个基本情况及其变体的更好解决方案,然后看看用这种深度嵌套做的一些有趣的事情!
基本解决方案
我们需要的是递归方法。例如,使用 Haml,以下代码段可以解决问题
- def nest(cls, n);
- return '' unless n > 0;
- "<div class='#{cls}'>#{nest(cls, n - 1)}</div>"; end
= nest('👻', 5)
那里有一个表情符号类,因为我们可以这样做,因为这只是一个有趣的小例子。我绝对不会在实际网站上使用表情符号类,但在其他情况下,我喜欢在我的代码中添加一些乐趣。
我们还可以生成 使用 Pug 的 HTML
mixin nest(cls, n)
div(class=cls)
if --n
+nest(cls, n)
+nest('👻', 5)
然后还有 JavaScript 选项
function nest(_parent, cls, n) {
let _el = document.createElement('div');
if(--n) nest(_el, cls, n);
_el.classList.add(cls);
_parent.appendChild(_el)
};
nest(document.body, '👻', 5)
使用 PHP,我们可以使用类似的东西
<?php
function nest($cls, $n) {
echo "<div class='$cls'>";
if(--$n > 0) nest($cls, $n);
echo "</div>";
}
nest('👻', 5);
?>
请注意,这些代码产生的主要区别与格式和空格有关。这意味着,使用 .👻:empty
针对最里面的“boo”将在 Haml、JavaScript 和 PHP 生成的 HTML 中起作用,但在 Pug 生成的 HTML 中将失败。
添加层级指示器
假设我们希望每个 boos 都有一个层级指示器,作为自定义属性 --i
,然后可以用来为每个 boos 提供不同的 background
,例如。
您可能会认为,如果我们只想改变色调,那么我们可以使用 filter: hue-rotate()
而不使用层级指示器。但是,hue-rotate()
*不仅会影响色调,还会影响饱和度和亮度*。它也不能像使用我们自己的依赖于层级指示器 --i
的自定义函数那样提供相同级别的控制。
例如,这是我在最近的一个项目中使用的东西,以便让 background
组件从一个级别平滑地过渡到另一个级别($c
值是多项式系数)
--sq: calc(var(--i)*var(--i)); /* square */
--cb: calc(var(--sq)*var(--i)); /* cube */
--hue: calc(#{$ch0} + #{$ch1}*var(--i) + #{$ch2}*var(--sq) + #{$ch3}*var(--cb));
--sat: calc((#{$cs0} + #{$cs1}*var(--i) + #{$cs2}*var(--sq) + #{$cs3}*var(--cb))*1%);
--lum: calc((#{$cl0} + #{$cl1}*var(--i) + #{$cl2}*var(--sq) + #{$cl3}*var(--cb))*1%);
background: hsl(var(--hue), var(--sat), var(--lum));
调整 Pug 以添加层级指示器如下所示
mixin nest(cls, n, i = 0)
div(class=cls style=`--i: ${i}`)
if ++i < n
+nest(cls, n, i)
+nest('👻', 5)
Haml 版本 也不太一样
- def nest(cls, n, i = 0);
- return '' unless i < n;
- "<div class='#{cls}' style='--i: #{i}'>#{nest(cls, n, i + 1)}</div>"; end
= nest('👻', 5)
使用 JavaScript,我们有
function nest(_parent, cls, n, i = 0) {
let _el = document.createElement('div');
_el.style.setProperty('--i', i);
if(++i < n) nest(_el, cls, n, i);
_el.classList.add(cls);
_parent.appendChild(_el)
};
nest(document.body, '👻', 5)
使用 PHP,代码如下所示
<?php
function nest($cls, $n, $i = 0) {
echo "<div class='$cls' style='--i: $i'>";
if(++$i < $n) nest($cls, $n, $i);
echo "</div>";
}
nest('👻', 5);
?>
更像树的结构
假设我们希望每个 boos 都有两个 boos 子节点,形成如下结构
.boo
.boo
.boo
.boo
.boo
.boo
.boo
.boo
.boo
.boo
.boo
.boo
.boo
.boo
.boo
幸运的是,我们不需要对基本 Pug 混合函数进行太多更改来实现这一点(演示)
mixin nest(cls, n)
div(class=cls)
if --n
+nest(cls, n)
+nest(cls, n)
+nest('👻', 5)
Haml 版本 也是如此
- def nest(cls, n);
- return '' unless n > 0;
- "<div class='#{cls}'>#{nest(cls, n - 1)}#{nest(cls, n - 1)}</div>"; end
= nest('👻', 5)
JavaScript 版本 需要更多努力,但不多
function nest(_parent, cls, n) {
let _el = document.createElement('div');
if(n > 1) {
nest(_el, cls, n - 1);
nest(_el, cls, n - 1)
}
_el.classList.add(cls);
_parent.appendChild(_el)
};
nest(document.body, '👻', 5)
使用 PHP,我们只需要在 if
块中再次调用 nest()
函数一次
<?php
function nest($cls, $n) {
echo "<div class='$cls'>";
if(--$n > 0) {
nest($cls, $n);
nest($cls, $n);
}
echo "</div>";
}
nest('👻', 5);
?>
以不同的方式为顶级元素设置样式
当然,我们可以为顶级添加一个特殊的 .top
(或 .root
或类似的)类,但我更喜欢将其留给 CSS
:not(.👻) > .👻 {
/* Top-level styles*/
}
小心!
某些属性,例如 transform
、filter
、clip-path
、mask
或 opacity
,不仅会影响元素本身,还会影响其所有后代。有时这是预期的效果,也是嵌套这些元素优于它们作为兄弟元素的原因。
但是,其他时候它可能不是我们想要的,虽然可以反转 transform
的效果,有时甚至可以反转 filter
的效果,但我们对其他效果无能为力。例如,我们无法在元素上设置 opacity: 1.25
来弥补其父元素的 opacity: .8
。
例子!
首先,我们有这个我最近为 CodePen 挑战制作的纯 CSS 点加载器
在这里,缩放变换和动画旋转的效果在内部元素上累加,不透明度也是如此。
接下来是这个阴阳舞,它使用树状结构
对于每个项目,除了最外面的项目(:not(.☯️) > .☯️
)外,直径等于其父元素直径的一半。对于最里面的项目(.☯️:empty
,我想我们可以称之为树叶),background
还有两个额外的 radial-gradient()
层。就像第一个演示一样,动画旋转的效果在内部元素上累加。
另一个例子是这些旋转的糖果触手
每个同心圆都代表一个嵌套级别,并将所有祖先的动画旋转效果与其自身的效果相结合。
最后,我们有这个三角形开口演示(请注意,它使用的是单独的变换属性,例如 rotate
和 scale
,因此需要在 chrome://flags
中启用 实验性 Web 平台功能 标志才能在 Chromium 浏览器中看到它工作)
它使用的是基本嵌套混合函数的略微修改版本,以便在每个级别上也设置 color
- let c = ['#b05574', '#f87e7b', '#fab87f', '#dcd1b4', '#5e9fa3'];
- let n = c.length;
mixin nest(cls, n)
div(class=cls style=`color: ${c[--n]}`)
if n
+nest(cls, n)
body(style=`background: ${c[0]}`)
+nest('🔺', n)
这里动画的是单独的变换属性 scale
和 rotate
。这样做是为了我们可以为它们设置不同的时间函数。
当我看到你在 PHP 代码中使用双引号时,我就不再读了。
嘿,这很酷!请注意,当您添加第二个嵌套调用时,您的 JavaScript 函数中存在无限递归
已修复,谢谢!
虽然我以前使用过 Pug,但我从未想到您可以用它简化重复的 SVG 标记。但一旦这个想法出现在我的脑海中……
总有一天,我们必须进行一场 CSS 对 SVG 的代码高尔夫比赛。创意和编码时间,你会直接获胜。但行数,我向你挑战。
对于这个旋转器
你用纯 CSS 有 6 行 Pug 和 32 行 SCSS
我用纯 SVG 有 20 行 Pug 和 9 行 Sass
查看 Pen
jOrMjLW by ccprog (@ccprog)
在 CodePen 上。
也就是说,没有设定精确的规则。我认为我们都在遵循“不压缩,每行不超过 80 个字符”的原则。
我刚刚尝试为 Web 开发代码高尔夫制定一个规则集:拟议挑战的沙盒
我怀疑像我一样,你们大多数人可能没有见过这个 StackExchange 社区,但我之所以去那里,是因为那里有专家,可以制定出良好的规则集,不受语言背景的限制。但我希望得到更广泛的前端社区的意见。由于该提议位于一个答案中,因此改进可能是在评论中进行的。有关此沙盒的描述,请参阅其问题。
这是一个有趣的想法。
我认为如果我要使用 SVG,我会选择 类似这样的东西,这样在编译后的代码中也不会有重复。我认为这比你的示例需要写更多的代码,但它会产生一个明显更小的 SVG(令人沮丧的是,除非我在每个
circle
上设置pathLength
,否则它不起作用,因为这是真正让我的视网膜感到不适的重复源之一)。*有趣的是,自从 CSS 变量成为主流后,我实际上开始更多地依赖 Pug 来简化编译后的代码。预处理器代码已经相当紧凑,因为循环是我在 2013 年开始使用 HTML 和 CSS 预处理器的初衷,但有了变量,我可以使用 Pug 生成一堆元素,为它们提供内联设置的索引自定义属性,然后使 CSS 属性值依赖于这些索引,而不是为 Sass 中的每个元素生成一个
nth-child
块。如果你也考虑了编译后的长度,这里有一个经过优化的混合版本。我坦白地说,CSS 变换语法更短,所以我将其整合进来,并进一步优化了
<use>
的使用方式。新的数字是(字节没有应用压缩)
你的:Pug 6 行,SCSS 32 行,总计:40 行
编译后的 HTML 129 字节,CSS 1744 字节,总计:1873 字节
我的,第一个版本:Pug 20 行,Sass 9 行,总计:29 行
编译后的 HTML 2563 字节,CSS 127 字节,总计:2690 字节
混合:Pug 15 行,CSS 14 行,总计:29 行
编译后的 HTML 736 字节,CSS 266 字节,总计:1002 字节
非常抱歉,我错过了你的第二个版本
你的纯 SVG:17 行 + 12 行 = 29 行
861 字节 + 219 字节 = 1080 字节
会与旧版浏览器出现问题吗?
Emmet 不是为此而开发的吗?我错过了什么?
我绝对不明白你为什么不使用 Emmet。
在 VSCode 中有一个很好的示例(假设这里的一些读者是初学者或仍在使用 Notepad++),它是一个名为 Emmet Live 的扩展。你只需要写一句话,按下 Tab 键,就可以生成完整的代码。不需要任何 php。事情已经简化了,为什么要重新发明轮子呢?
除此之外,你的动画真的很棒;) 你提供了一个很好的示例,值得点赞!
谢谢!
首先,因为我并不确定 Emmet 到底是什么。当我第一次听到人们谈论它时,我以为它是一个文本编辑器,但现在我从你的描述中相信它是一个可以添加到不同 IDE 的扩展?无论如何,这不是我的菜。
一方面,我和 IDE 关系不佳。在我编写 C/C++、Java、PHP、Lisp 等语言时,我们就相处不好……现在仍然相处不好,以后也不会相处好。在 2002 年到 2011 年的十年间,我一直在显示器上贴着便签,上面写着来自我那些更精通的朋友的指示,但我仍然勉强度日(翻译:我从来不好,也没有做成什么大事)。
十年前,发现在线游乐场让我改变了生活,在那里你只需在一个框中编写代码,然后按运行按钮,就可以看到代码的结果。再也不用等三周,才能让某人最终弄明白你把 IDE 搞砸了什么,导致你无法运行任何东西。再也不用看到一堆让你困惑的按钮、菜单和选项。再也不用因为不小心按下了某个键或键组合(你甚至不知道是什么或如何撤销而不丢失工作)而导致东西出现/消失/以其他方式弄乱。
我可能会偶尔使用 Ubuntu 自带的基本文本编辑器,但这也有一个优势,它没有疯狂的功能,只会妨碍我。
无论如何,从我的理解来看,你的意思是为什么不使用一个扩展,它会在代码中插入完整的生成的嵌套 HTML?我不想看到那样。在我使用的代码中,我宁愿看到一个递归调用,而不是看到它生成的代码。这样更容易理解,我的屏幕上的垂直空间有限,我发现滚动浏览代码非常令人困惑,所以代码越短越好。
至于动画部分,我对此有点摸不着头脑,因为动画在这些演示中是最简单的事情。除了最后一个,因为没有浏览器支持 CSS 三角函数,但我想既然我用三角函数从 JS 中 模拟 CSS 定时函数,我就可以在 CSS 中做相反的事情。