关于使用传统2D轮播的说法很多,例如,《粉碎杂志》上的这一篇文章涉及这一主题。 “ 我应该使用轮播吗? ”没有简单的是或否答案。 ' 题; 这取决于具体情况。
当我开始研究此主题时,我并不需要3D轮播,而是对实现它的技术细节更感兴趣。 当然,所采用的核心技术来自CSS转换模块1级 ,但是在此过程中,还将应用其他一系列前端开发技术,涉及CSS, Sass和客户端JavaScript的各种主题。
该CodePen显示组件的不同版本,我将向您展示如何构建 。
为了说明CSS 3D转换的设置,我将向您展示该组件的仅CSS版本。 然后,我将向您展示如何使用JavaScript增强它,并开发一个简单的组件脚本。
对于标记,组件内部的图像被包装在元素内,该元素提供了基本的骨架:
这将是我们的出发点。
在研究CSS之前,我们先概述一下将在以下各节中制定的计划。
元素应围绕旋转木马划定的圆圈排列。 该圆可以通过其外接的正多边形和放置在其侧面的图像来近似:
因此,该多边形的边数与轮播中的图像数相同:对于三个图像,该多边形为等边三角形; 有四个图像是一个正方形; 有五个五边形; 等等:
见笔正多边形由SitePoint( @SitePoint上) CodePen 。
如果轮播中的图像少于三个,该怎么办? 无法定义多边形,因此无法原样应用以下步骤。 无论如何,仅一张图像的情况就毫无用处。 两个图像的可能性更大,可以将它们放置在圆上两个相对的点上。 为简单起见,不处理这些特殊情况,并且假定至少三个图像。 但是,相对的代码修改并不难。
该虚构参考多边形将位于3D空间中,垂直于视口平面,并且其中心以等于其顶点 (即多边形的一面距其中心的距离)的距离推回到屏幕中,如图所示。旋转木马的俯视图:
这样,当前面对观察者的一侧将位于屏幕平面上,z = 0,并且不受透视透视缩短影响的正面图像将具有其普通的2D尺寸。 图片中的d
字母表示CSS perspective
属性的值。
在本节中,我将向您展示关键的CSS规则,并将逐步进行介绍。
在以下代码段中,一些Sass变量用于使组件更具可配置性。 我将使用$n
表示轮播中的图片数量,并使用$item-width
来指定图片的宽度。
元素是第一个图像和其他图像围绕其定位和变换的参考元素的包含框。 假设就目前而言,该轮播仅展示一张图片,我可以从调整大小和对齐方式开始:
.carousel {
display: flex;
flex-direction: column;
align-items: center;
> * {
flex: 0 0 auto;
}
.figure {
width: $item-width;
transform-style: preserve-3d;
img {
width: 100%;
&:not(:first-of-type) {
display: none /* Just for now */
}
}
}
}
元素具有规定的轮播项目宽度,并且具有相同的图像高度(它们可以具有不同的大小,但必须具有相同的纵横比)。 这样,轮播容器的高度会根据图像的高度自行调整。 同样,
在转盘容器中水平居中。
第一个图像不需要其他转换,因为它已经在其目标位置,即在转盘的前面。
通过对元素应用旋转变换,可以在3D空间中旋转圆盘传送带。 该旋转必须围绕多边形的中心,因此我将更改默认的变换原点
:
.carousel figure {
transform-origin: 50% 50% (-$apothem);
}
否定此值是因为在CSS中,z轴的正方向朝着查看者偏离了屏幕。 需要使用括号以避免Sass语法错误。 多边形的运算心距将在稍后解释。
平移元素的参考系统后,可以在其(新)y轴上旋转旋转整个旋转木马:
.carousel figure {
transform: rotateY(/* some amount here */rad);
}
稍后我将返回此轮换的详细信息。
让我们继续其他图像的转换。 通过绝对定位,图像堆叠在内部:
.carousel figure img:not(:first-of-type) {
position: absolute;
left: 0;
top: 0;
}
z-index
值将被忽略,因为这只是以下转换的预备步骤。 实际上,现在每个图像都可以在旋转木马的y轴上旋转一个旋转角度,该旋转角度取决于为其分配图像的多边形一侧。 首先,与元素一样,修改图像的默认变换原点,将其移至多边形的中心:
.img:not(:first-of-type) {
transform-origin: 50% 50% (-$apothem);
}
然后图像可以在其新的y轴上旋转($i - 1) * $theta
radians给定的量,其中$i
是图像的索引(从一个开始), $theta = 2 * $PI / $n
,其中$PI
代表数学常数pi 。 因此,第二个图像将旋转$theta
,第三个图像将旋转2 * $theta
,依此类推,直到最后一个图像将旋转($n - 1) * $theta
。
由于嵌套CSS转换的层次性质,在轮播旋转期间(即围绕的修改后的y轴旋转),将保留图像的这种相对排列。
可以使用Sass @for
控制指令分配每个图像的旋转量:
.carousel figure img {
@for $i from 2 through $n {
&:nth-child(#{$i}) {
transform: rotateY(#{($i - 1) * $theta}rad);
}
}
}
这是使用for...through
构造而不是for...to
因为使用for...to
分配给索引变量$i
的最后一个值将是n-1
而不是n
。
注意Sass的#{}
插值语法的两个实例。 在第一种情况下,它用于:nth-child()
选择器的索引; 在第二种情况下,它用于设置旋转属性值。
多边形的矩的计算取决于边的数量和边的宽度,即$n
和$item-width
变量。 公式为:
$image-width / (2 * tan($PI/$n))
其中tan()
是正切三角函数 。
该公式可以用一些几何和三角函数导出。 在笔源中,此公式未按书面形式实现,因为切线函数在Sass中不易使用,因此改用硬编码值。 该公式将完全在JavaScript演示中实现。
此时,轮播图像被并排“缝制”,形成所需的多边形形状。 但是在这里它们紧密地包装在一起,而在3D轮播中,它们之间经常有空间。 此距离增强了对3D空间的感知,因为它使您可以看到圆盘传送带背面的背面图像。
通过引入另一个配置变量$item-separation
并将其用作每个元素的水平填充,可以有选择地在图像之间添加此间隙。 更准确地说,将这个值的一半用于左右填充:
.carousel figure img {
padding: 0 $item-separation / 2;
}
最终结果可以在以下演示中看到:
请参见在CodePen上由SitePoint( @SitePoint ) 隔开的Pen Carousel项目 。
使图像具有opacity
属性以使其更加透明,以更好地说明圆盘传送带的结构,并且圆盘传送带根元素上的柔韧性布局用于将其在视口中垂直居中。
为了便于测试转盘旋转,我将添加一个UI控件来在图像之间来回导航。 有关实现此控件的HTML,CSS和JavaScript,请参见CodePen演示。 在这里,我将仅描述与旋转有关的代码。
我们使用currImage
整数变量来指示哪个图像位于轮播的前面。 当用户与上一个/下一个按钮交互时,此变量以一个单位递增或递减。
更新了currImage
,轮播旋转将通过以下方式执行:
figure.style.transform = `rotateY(${currImage * -theta}rad)`;
( 在这里和以下代码段中,使用ES6模板文字对字符串中的表达式进行插值;如果愿意,可以随意使用传统的'+'串联运算符 )
其中theta
与以前相同:
numImages = figure.childElementCount;
theta = 2 * Math.PI / numImages;
旋转角度为- theta
因为要导航到下一个项目,需要逆时针旋转,并且这种旋转值在CSS变换中为负。
请注意, currImage
值currImage
于[0,numImages – 1]范围,而是可以无限地沿正方向和负方向增长。 实际上,如果前面的图像是最后一个图像(所以currImage
== n-1),并且用户单击了下一个按钮,如果我们将currImage
重置为0以前进到第一个轮播图像,则会发生过渡从(n-1)*theta
到0的旋转角度,这将使轮播在所有先前的图像上朝相反的方向旋转。 当正面图像是第一个图像时,单击prev按钮会发生类似的问题。
要挑剔,我什至应该检查currentImage
潜在溢出,因为Number
数据类型不能采用任意大的值。 这些检查未在演示代码中实现。
这是旋转的旋转木马:
请参阅CodePen上的SitePoint ( @SitePoint )的Pen 3D旋转转盘(增强之前) 。
看到了旋转木马核心的基本CSS之后,现在可以使用JavaScript以多种方式增强组件,例如:
首先,我从样式表中删除与转换原点和旋转相关的变量和规则,因为这些将使用JavaScript完成:
$item-width: 40%; // Now we can use percentages
$item-separation: 0px; // This now is set with Js
$viewer-distance: 500px;
.carousel {
padding: 20px;
perspective: $viewer-distance;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
> * {
flex: 0 0 auto;
}
figure {
margin: 0;
width: $item-width;
transform-style: preserve-3d;
transition: transform 0.5s;
img {
width: 100%;
box-sizing: border-box;
padding: 0 $item-separation / 2;
&:not(:first-of-type) {
position: absolute;
left: 0;
top: 0;
}
}
}
}
脚本中的下一个是carousel()
函数,该函数负责实例的初始化:
function carousel(root) {
// coming soon...
}
root
参数是指持有轮播的DOM元素。
通常,此函数将是一个构造函数,以便为页面上的每个轮播生成一个对象,但是这里我没有编写轮播库,因此一个简单的函数就足够了。
要实例化同一页面上的几个组件,代码将等待所有图像加载完毕,在window对象上为load
事件注册一个侦听器,然后为每个带有carousel
类的元素调用carousel()
:
window.addEventListener('load', () => {
var carousels = document.querySelectorAll('.carousel');
for (var i = 0; i < carousels.length; i++) {
carousel(carousels[i]);
}
});
carousel()
执行三个主要任务:
在检查转换设置代码之前,我将介绍一些关键变量以及如何根据实例配置对其进行初始化:
var
figure = root.querySelector('figure'),
images = figure.children,
n = images.length,
gap = root.dataset.gap || 0,
bfc = 'bfc' in root.dataset
;
根据元素的子元素数量初始化图像数量(
n
)。 幻灯片之间的gap
( gap
)是从HTML5 data-gap
属性(如果已设置)初始化的。 使用HTML5的数据集API读取背面可见性标志( bfc
)。 稍后将使用它来确定转盘背面的图像是否可见。
设置CSS转换相关属性的代码封装在setupCarousel()
。 此嵌套函数带有两个参数。 第一个是轮播中的项数,即上面介绍的n
变量。 第二个参数s
是轮播多边形的边的长度。 正如我之前提到的,这等于图像的宽度,因此可以使用getComputedStyle()
读取其中之一的当前宽度:
setupCarousel(n, parseFloat(getComputedStyle(images[0]).width));
这样,可以使用百分比值设置图像宽度。
为了使轮播保持响应,我为窗口resize
事件注册了一个侦听器,该事件再次使用(最终)修改后的图像大小再次调用setupCarousel()
:
window.addEventListener('resize', () => {
setupCarousel(n, parseFloat(getComputedStyle(images[0]).width));
});
为了简单起见,我没有对调整大小的侦听器进行反弹 。
setupCarousel()
所做的第一件事是使用传递的参数和前面讨论的公式来计算多边形的setupCarousel()
:
apothem = s / (2 * Math.tan(Math.PI / n));
然后,使用此值来修改图形元素的变换原点,以获得轮播的新旋转轴:
figure.style.transformOrigin = `50% 50% ${-apothem}px`;
接下来,应用图像的样式:
for (var i = 0; i < n; i++) {
images[i].style.padding = `${gap}px`;
}
for (i = 1; i < n; i++) {
images[i].style.transformOrigin = `50% 50% ${- apothem}px`;
images[i].style.transform = `rotateY(${i * theta}rad)`;
}
if (bfc) {
for (i = 0; i < n; i++) {
images[i].style.backfaceVisibility = 'hidden';
}
}
第一个循环为轮播项目之间的空间分配填充。 第二个周期设置3D变换。 如果在轮播配置中指定了相关标志,则最后一个循环处理背面。
最后, rotateCarousel()
将当前图像显示在最前面。 这是一个小辅助功能,给定要显示图像的索引,该功能就将图形元素沿其y轴旋转以将目标图像移到前面。 导航代码也使用它来回移动:
function rotateCarousel(imageIndex) {
figure.style.transform = `rotateY(${imageIndex * -theta}rad)`;
}
这是最终结果,一个演示实例化了几个轮播,每个轮播具有不同的配置:
请参阅CodePen上SitePoint ( @SitePoint ) 提供的带有CSS和JavaScript的Pen 3D旋转 圆盘 传送带 。
在结束之前,我只想赞扬一些用于研究本教程的资源:
如果您对代码或轮播功能有任何疑问或意见,请随时将其留在下面。
From: https://www.sitepoint.com/building-3d-rotating-carousel-css-javascript/