无需 HTML 或 JS 即可构建 CSS 游戏

2025-06-04

无需 HTML 或 JS 即可构建 CSS 游戏

这一切都始于@jheyy 的一个演示(由于某种原因,它似乎不再在我的计算机上运行),其中他没有使用单个 HTML 元素,就创建了一个马里奥赛车动画……然后是一条推文:

我对这个挑战感到很兴奋,但不得不承认,我尝试修改JHey的代码,但没能完全解决它。我让它做了一些事情,但效果不大……所以我决定自己独立做点什么。虽然不会那么花哨,但至少能让它正常工作。

结果在这个 CodePen 上。你也可以在 Youtube 上观看演示

注意:我只是提供了演示的链接,而不是将其嵌入到文章中,因为 WebKit 浏览器(Chrome、Safari、Brave、新版 Edge 等)似乎在处理大型动画和倾斜元素时存在问题,导致浏览器速度变慢或颜色闪烁。该演示在 Firefox 上效果会更好

该频道上还有更多精彩的 CSS 视频,立即订阅!

该游戏实际上可以运行(有一些怪癖,我们稍后会看到)。并且,正如在链接的 Codepen 中看到的那样,它由以下部分组成:

  • 200 行 CSS(包括注释)。
  • 0 行 JavaScript
  • 0 行 HTML

HTML 的零行只是一种假象:仍然有一个带有<html><body>标签的基本 HTML 结构(正如我们在另一篇文章中解释的那样)...我们将使用它们来生成游戏。

这是什么巫术?是怎么做到的?我们来看看。

但在此之前,我要说的是,这是一种有趣的 Web 开发和 CSS 方法,非常适合练习和学习,但它几乎没有实际效果。结果是一个不错的演示,但千万不要在生产网站上尝试这样的东西。

如何完成

本文将包含大量详细的解释,但如果您想观看该过程的视频,您可以在我的 Youtube 频道上观看:

20倍速的视频仅持续7分钟

即使我们只有两个元素(<html><body>),如果我们算上每个元素的::before和伪元素,我们就可以设置六种样式。::after

本场比赛仅使用了六个中的五个。

:背景html和光标

遗嘱的格式html包括三个重要部分:

  1. 游戏的基本背景是蓝天和绿色的地面。
  2. 一个看起来像汽车的定制光标。
  3. 透视,因此一些元素看起来是三维的(我们稍后会看到)。

背景可以用 来实现linear-gradient。天空部分不用太花哨,因为稍后会被其他元素覆盖。地面也会包含一些其他的 ,radial-gradient所以它不仅仅是纯绿色,而是拥有不同的色调和形状。

自定义光标可能更有趣一些。在 CSS 中,我们可以通过cursor属性指定要使用的光标。我们可以从系统光标列表中选择,也可以使用 来设置外部图像url()

我们希望将所有代码都封装在 CSS 中,因此我们选择了内联 SVG 而不是外部图片文件。您可以使用如下协议添加内联 SVG :data:

html {
  cursor: url(data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'>...</svg>), auto;
}
Enter fullscreen mode Exit fullscreen mode

指定自定义游标时有两个关键事项:

  1. 您必须始终包含系统回退。否则,浏览器将不会将图像显示为光标。
  2. 内联 SVG 必须包含该xmlns属性,否则 SVG 将不会被呈现。

对于内联 SVG,我们使用矩形和路径来模拟一辆小型汽车。结果简单却引人注目:

从后面看多边形汽车的卡通画

并不完美,但对于一行 CSS/SVG 来说还不错

关于汽车的最后一个细节:汽车的左上角是实际的指针;这并不自然,因为你可以越过其中一个障碍物而不会迷路。

cursor属性允许设置指针在光标内的位置坐标。不过,该值必须小于 32。因此,我们相应地修改 CSS:

html {
  cursor: url(data:image/svg+xml;utf8,...) 31 31, pointer;
}
Enter fullscreen mode Exit fullscreen mode

html::before:道路

html由于我们说过稍后会看到的透视图,我们可以用::before伪元素创建一个灰色矩形,并应用3D 旋转,使道路看起来像是逐渐消失,在远处变得越来越小。

这个作品的难点在于尺寸。最初高度是 100%,但旋转后就没到地平线的边缘了。我们不得不手动调整高度,让它们对齐。

注意:有一个数学公式可以计算精确的高度。但尝试一下更快捷的方法,几秒钟内就能得到足够接近的值。

为了绘制道路,我们使用两个线性渐变:

  • 一条水平线表示铺设区域(透明-灰色-透明)。
  • 一条垂直线代表红色和白色的肩线。

最后,我们创建一个小动画,让背景从上到下移动,模拟汽车的行驶。这就是相对论!汽车静止不动;是整个世界在它的轮胎下移动。

@keyframes moveRoad {
  0% { background-position: 0 0; }
  100% { background-position: 0 16.3vh; }
}
Enter fullscreen mode Exit fullscreen mode
值为 16.3vh 是因为道路为 163vh 并且有 10 套红白色油漆。

后来我们添加了一些阴影和模糊来稍微修饰一下道路。不过,没什么特别的。

html::after:天空

html::after伪元素用于绘制天空和地平线上的一些山脉,但它也服务于一个根本目的:隐藏主体的溢出

正如我们将在下一节中看到的那样,它body会“溢出”容器,看起来像是飞上了天空。为了避免这种情况,我们将天空放在它上面。然后用太阳、一些山脉、一两棵树来装饰它……它看起来就像一个护身符。

我们用“径向渐变”来生成太阳和圆润的山脉;对于山脉和树木,我们使用了linear-gradient。然后稍微调整了一下背景的大小和位置,让所有东西都到位。

游戏中天空的截图,上面写着“linear-gradient”字样,箭头指向一座山和一棵树干;还有“radial-gradient”字样,箭头指向太阳、一些圆形的山脉和树梢。

用于生成地平线和天空的不同渐变

body:乐趣开始了

这是一个棘手的部分,如果你看过视频,就会发现我的头撞到了桌子上。

这个想法是利用车身来创建一条汽车必须遵循的路径,以“赢得游戏”(游戏永远不会结束,所以唯一的选择就是最终失败)。

首先,我们首先将它制作成一个非常高的元素,然后以与道路旋转相同的角度倾斜它;这样,它就会与道路重叠。

作者注:我的问题就是从这里开始的。我当时在 WebKit 浏览器上编写代码,它开始难以处理较大的倾斜元素,尤其是在裁剪和添加动画效果之后。这时,我把开发浏览器换成了 Firefox,现在看起来运行良好了。

我们可以通过裁剪主体来实现这一点clip-path。我们只保留部分轨道,并使用之字形路径,同时移除/裁剪其余部分。

现在,汽车/老鼠可以在 上面从右向左移动(反之亦然),body但无需将鼠标悬停在其上方,因为它已被剪切 - 是时候用 画一些油渍了radial-gradient

没有天空的游戏截图,马路上的油渍飞到天边之外

如果没有html::after,道路就会出现在地平线上

然后我们添加动画,让道路/障碍物从上向下移动。我们可以使用translate变换来实现(现在不需要 3D 动画了)。

剩下唯一要做的就是当汽车/老鼠驶过油渍时停止游戏。为此,我们还剩下两个伪元素……但我们只需要一个。

body::after:结束画面

伪元素body::after将是一个 Game Over 屏幕。默认情况下,它是不可见的(display: none),只有当鼠标移到body(例如,当汽车驶过油渍时)才会显示。

我们还必须设置样式body。在这种情况下,重要的是:

  • 删除clip-path。否则游戏结束消息也会被裁剪!
  • 调整视口的高度。而不是一个很高的容器。
  • 移除 3D 旋转。否则游戏结束信息也会倾斜!
  • 删除动画。这样消息就不会直接滑出视口了。
  • 添加 z-index。这样body就位于 之上html::after

我们可以通过使用以下几条规则重置样式来完成所有这些操作:

body:hover {
  transform: none;
  width: 100vw;
  height: 100vh;
  top: 0;
  left: 0;
  z-index: 1;
  clip-path: none;
  animation: none;
  background: none;
}

body:hover::after {
  display: flex;
}
Enter fullscreen mode Exit fullscreen mode

我们使用了Flexbox ,这样body::after我们就可以轻松地使用 和align-items来居中文本justify-content

::before在/::after伪元素中添加多行内容的一个技巧是将 放在\a新行应该出现的位置,然后添加 ,white-space: pre以便保留空格:

body::after {
  content: "GAME OVER \a\a Move the mouse \a outside of the window \a to restart the game";
  white-space: pre;
  /* ... */
}
Enter fullscreen mode Exit fullscreen mode

额外功能和关注点

自定义设置

这个游戏的一个很酷的地方——或者至少认为它很酷——是它可以轻松配置。

通过用CSS 变量替换一些值,我们只需更新一行代码就能调整不同的动画和游戏设置。说真的,这很酷吧?

在这个游戏中,我们添加了三个设置:

  • 速度:油渍从上向下移动的动画时间(以秒为单位)。数值越低,游戏速度越快(难度也越大)。
  • Timing:动画计时函数。默认情况下,它是“线性”的,这意味着动画始终保持相同的速度。我们可以将其更改为任意值,让游戏以不同的速度移动,从而增加难度。
  • Animation:动画迭代次数。默认情况下,它是“无限的”,这意味着游戏将持续运行,但你可以将其更改为任意数字……说实话,这不是最好的变量名。

作者注:我尝试添加第四个 CSS 变量,以便玩家能够选择游戏的高分辨率/低分辨率版本(在小屏幕或低分辨率下运行效果更佳)。但结果不如预期……以后会再改进。

问题:hover

玩家如果输了,就必须将汽车/老鼠放在油渍上……但有一个问题:只有老鼠移动时才会触发:hover状态。

如果玩家将汽车放置在道路中间,然后松开鼠标,浏览器将不会触发:hover效果,游戏永远不会结束。

游戏截图,汽车在油污上行驶,没有游戏结束消息

不移动鼠标就能欺骗游戏

从安全角度来看,这种行为是有意义的:我们不想:hover无意中触发副作用(例如,没有任何用户交互)......这对这个游戏来说有点不幸(对于这个游戏也是如此)。

另外,还有一个陷阱。由于视角的原因,body当它在屏幕上向上移动时会“收缩”。这意味着页面的侧面没有被覆盖body,可以用来欺骗游戏。

一个可能的解决方案是将 的宽度body加大,比如说200vw而不是100vw。这可以解决问题,但会使动画变得困难和滞后。

一个提高性能的方案是使用实际图像作为背景和图像污点。这样浏览器就不需要进行太多计算,动画也会更流畅。

最后的想法

开发这款游戏非常有趣。代码编写大约花了两个半小时(上面的视频是20倍速的,所以只有7分钟),还需要一些额外的时间进行规划。

正如我们上面提到的,这并不实用,尽管这是学习和探索 CSS 的好方法。

遗憾的是,它在 WebKit 浏览器(目前是功能最丰富的浏览器)上运行得不够流畅。新的挑战是开发一个没有那么多 3D 旋转和动画的第二个版本,这样应该能更好地适应这些浏览器。

文章来源:https://dev.to/alvaromontoro/building-a-css-game-without-html-or-js-1m30
PREV
构建您自己的颜色对比度检查器
NEXT
7 个有趣的 HTML 属性(你可能不知道)