CSS 3D - 沿 z 轴滚动
在本文中,我们将创建一个小型 3D 场景,用户可以在 z 轴上滚动。您可以在GitHub上找到本教程的最终代码,也可以点击此链接查看演示。
本文假设你已经具备一些 CSS 和 JS 知识。我们将使用 CSS 自定义属性,如果你不熟悉 CSS 自定义属性,可以阅读CSS 自定义属性 - 速查表。
CSS 3D 简介
当我们谈论 CSS 3D 时,我们实际上谈论的是 CSS3 transform 3D。此方法允许我们使用transform
CSS 属性为 DOM 元素设置 z 轴上的透视或旋转。
transform CSS 属性允许您旋转、缩放、倾斜或平移元素。它修改了 CSS 视觉格式模型的坐标空间。
为了能够在 3D 空间中渲染我们的 Dom 元素,我们需要查看以下属性:
- 看法
- 透视原点
- 变换 Z
看法
perspective
是一个 CSS 属性,用于设置 z=0 和用户之间的距离。透视值越小,视角的扭曲程度就越大。(请尝试scenePerspective
在下面的 codePen 示例中更改该值)。
.container-scene {
perspective: 100px;
}
的值perspective
是长度单位。
尝试将下面的示例中的值设置scenePerspective
为 0 和 70。您会注意到,如果将值设置为 0,我们的立方体将完全没有透视效果。如果将值设置为 70,您会看到立方体透视效果非常明显。透视值越小,透视效果越明显。
为了能够渲染 3D 空间,我们需要transform-style: preserve-3d;
在子元素上指定。在上面的例子中,它设置为 our .cube
。默认情况下,元素是扁平化的。
.container-scene {
perspective: 400px;
}
.container-scene .cube {
transform-style: preserve-3d;
}
透视原点
该
perspective-origin
CSS 属性决定了观看者所注视的位置。它被 perspective 属性用作消失点。
这个属性基本上允许我们移动 3D 场景的消失点。
.container-scene {
perspective: 400px;
perspective-origin: 50% 100%; /*X position value, Y position value*/
}
.container-scene .cube {
transform-style: preserve-3d;
}
对于 x 和 y,我们可以使用百分比来设置位置。但我们也可以使用以下值:
- x位置:
left
= 0%center
= 50%right
= 100%
- y 位置
top
= 0%center
= 50%bottom
= 50%
perspectiveOriginX
在下面的示例中,您可以更改和的值perspectiveOriginY
。
变换 Z
我们之前已经提到过,transform
CSS 属性允许我们在 3D 空间中设置元素。
Transform 具有不同的功能来在 3D 中转换我们的元素:
正如我们在本节插图中看到的perspective
,translateZ()
允许我们沿 3D 空间的 z 轴定位元素。或者,我们也可以使用translate3D(x, y, z)
CSS 函数。
在下面的例子中,您可以通过改变和的值来调整.cube
和的 Z 轴位置。.face-
cubeTranslateZ
cubeFacesTranslateZ
沿 z 轴滚动
现在我们已经很好地了解了 CSS 3D 的工作原理,我们将创建一个 3D 场景,我们将能够在 z 轴上滚动。
设置场景
我们将创建一个页面,列出吉卜力工作室的所有电影。每部电影都将以卡片的形式放置在场景的 z 轴上。欢迎 fork 或下载以下 codepen 作为入门资料,以便继续学习。我使用axios和Studio Ghibli API来填充此页面。
如果您想跟随自己的内容,我们将需要以下标记:
<div class="viewport">
<div class="scene3D-container">
<div class="scene3D">
<div>Card1</div>
<div>Card2</div>
<!--Etc.-->
</div>
</div>
</div>
造型
首先,我们要设置CSS 自定义属性(CSS 变量)。其中一些变量将使用 JS 进行转换。它们将帮助我们与场景进行交互。
:root {
--scenePerspective: 1;
--scenePerspectiveOriginX: 50;
--scenePerspectiveOriginY: 30;
--itemZ: 2; // Gap between each cards
--cameraSpeed: 150; // Where 1 is the fastest, this var is a multiplying factor of --scenePerspective and --filmZ
--cameraZ: 0; // Initial camera position
--viewportHeight: 0; // Viewport height will allow us to set the depth of our scene
}
.viewport
这将允许我们设置窗口的高度。稍后我们将使用它来设置场景的深度,并使用滚动条在z轴上导航。
.viewport {
height: calc(var(--viewportHeight) * 1px);
}
.scene3D-container
设置场景透视图和透视原点。它的位置是固定的,所以它始终在屏幕上。我们也要设置透视原点。
.viewport .scene3D-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
perspective: calc(var(--scenePerspective) * var(--cameraSpeed) * 1px);
perspective-origin: calc(var(--scenePerspectiveOriginX) * 1%) calc(
var(--scenePerspectiveOriginY) * 1%
);
will-change: perspective-origin;
transform: translate3d(
0,
0,
0
); //Allows Hardware-Accelerated CSS, so transitions are smoother
}
.scene3D
设置场景在 z 轴上的位置,这有点像在 z 轴上移动相机。但实际上我们移动的是场景,而相机(视口)是固定的。在本文的其余部分,我们将使用相机比较。.scene3D
取视口的完整高度和宽度。
.viewport .scene3D-container .scene3D {
position: absolute;
top: 0;
height: 100vh;
width: 100%;
transform-style: preserve-3d;
transform: translateZ(calc(var(--cameraZ) * 1px));
will-change: transform;
}
最后,同样重要的是,我们要在场景中定位卡片。所有卡片都是绝对定位的。奇数卡片位于左侧,偶数卡片位于右侧。
我们使用 SCSS 以编程方式平移每个项目。在X轴和Y轴上,我们随机地将它们平移 -25% 到 25%(X 轴),-50% 到 50%(Y 轴)。我们使用循环,@for
使每个项目在z轴上的平移量乘以其索引。
.viewport .scene3D-container .scene3D {
> div {
position: absolute;
display: block;
width: 100%;
top: 40%;
@media only screen and (min-width: 600px) {
width: 45%;
}
&:nth-child(2n) {
left: 0;
}
&:nth-child(2n + 1) {
right: 0;
}
@for $i from 0 through 25 {
&:nth-child(#{$i}) {
transform: translate3D(
random(50) - 25 * 1%,
random(100) - 50 * 1%,
calc(var(--itemZ) * var(--cameraSpeed) * #{$i} * -1px)
);
}
}
}
}
CSS 现在已经完成,我们有了一个 3D 场景。在本文的后续部分,我们将编写一些 JavaScript 代码来实现场景导航。
沿 z 轴滚动(移动相机)
为了能够滚动,我们首先需要设置--viewportHeight
模拟场景深度的值。
场景的深度等于下列各项的和:
- 用户窗口的高度
- 窗口内部高度
- 视角
.scene3D-container
var(--scenePerspective) * var(--cameraSpeed)
- 我们最后一项的平移 z 值
var(--itemZ) * var(--cameraSpeed) * items.length
让我们创建一个函数来更新加载时setSceneHeight()
的值。--viewportHeight
document.addEventListener("DOMContentLoaded", function() {
setSceneHeight();
});
function setSceneHeight() {
const numberOfItems = films.length; // Or number of items you have in `.scene3D`
const itemZ = parseFloat(
getComputedStyle(document.documentElement).getPropertyValue("--itemZ")
);
const scenePerspective = parseFloat(
getComputedStyle(document.documentElement).getPropertyValue(
"--scenePerspective"
)
);
const cameraSpeed = parseFloat(
getComputedStyle(document.documentElement).getPropertyValue("--cameraSpeed")
);
const height =
window.innerHeight +
scenePerspective * cameraSpeed +
itemZ * cameraSpeed * numberOfItems;
// Update --viewportHeight value
document.documentElement.style.setProperty("--viewportHeight", height);
}
我们的页面现在有了滚动条,但仍然无法滚动。我们需要添加一个事件监听器来监听用户的滚动操作。scroll 事件会调用一个moveCamera()
函数,它会将window.pageYOffset--cameraZ
的值更新为的值。
document.addEventListener("DOMContentLoaded", function() {
window.addEventListener("scroll", moveCamera);
setSceneHeight();
});
function moveCamera() {
document.documentElement.style.setProperty("--cameraZ", window.pageYOffset);
}
function setSceneHeight() {
// ...
}
移动摄像机角度
最后,让我们让场景更具动态效果。在mousemove 事件中,我们将更改scenePerspectiveOriginX
和的值scenePerspectiveOriginY
。这将产生相机移动的错觉。物品将保持在场景中的直线位置。如果您想让相机旋转得更逼真,可以在场景中应用rotate3d() 。
首先,我们将这两个变量的初始值存储在一个perspectiveOrigin
对象中,并设置一个perspectiveOrigin.maxGap
值来限制变量的最大值和最小值。例如,如果scenePerspectiveOriginY
等于 50%。当鼠标移动时,新值将在 40% 到 60% 之间。
const perspectiveOrigin = {
x: parseFloat(
getComputedStyle(document.documentElement).getPropertyValue(
"--scenePerspectiveOriginX"
)
),
y: parseFloat(
getComputedStyle(document.documentElement).getPropertyValue(
"--scenePerspectiveOriginY"
)
),
maxGap: 10
};
如果用户光标位于屏幕中心,我们将设置--scenePerspectiveOriginX
和的值--scenePerspectiveOriginX
作为初始值。光标离中心越远,这些值的增/减幅度就越大。如果用户移动到左上角,值会增加;如果移动到右下角,值会减少。
该moveCameraAngle()
函数将更新以下值:
xGap
并yGap
以百分比形式返回用户鼠标在 X 轴和 Y 轴上的位置,与窗口的中心进行比较。newPerspectiveOriginX
并newPerspectiveOriginY
返回新的视角原点。
document.addEventListener("DOMContentLoaded", function() {
window.addEventListener("scroll", moveCamera);
window.addEventListener("mousemove", moveCameraAngle);
setSceneHeight();
});
function moveCameraAngle(event) {
const xGap =
(((event.clientX - window.innerWidth / 2) * 100) /
(window.innerWidth / 2)) *
-1;
const yGap =
(((event.clientY - window.innerHeight / 2) * 100) /
(window.innerHeight / 2)) *
-1;
const newPerspectiveOriginX =
perspectiveOrigin.x + (xGap * perspectiveOrigin.maxGap) / 100;
const newPerspectiveOriginY =
perspectiveOrigin.y + (yGap * perspectiveOrigin.maxGap) / 100;
document.documentElement.style.setProperty(
"--scenePerspectiveOriginX",
newPerspectiveOriginX
);
document.documentElement.style.setProperty(
"--scenePerspectiveOriginY",
newPerspectiveOriginY
);
}
我们的场景完成了🎉。希望你喜欢这篇文章😃
资源
文章来源:https://dev.to/vinceumo/css-3d---scrolling-on-the-z-axis-45i9