In this tutorial we are going to experiment with perspective and use a radio input trick to create a fun css-only timeline-like structure. The idea is to show a teaser of an item and when clicking on the associated radio input, the content will expand and rotate in 3D, giving some depth to the whole thing. We’ll be using CSS 3D transform, transitions and sibling selectors.
在本教程中,我们将尝试透视图,并使用无线电输入技巧创建一个有趣的css-only-timeline-like结构。 这个想法是展示一个物品的预告片,当单击相关的无线电输入时,内容将以3D形式扩展和旋转,从而使整个事物具有一定的深度。 我们将使用CSS 3D变换,过渡和同级选择器。
Also note that the 3D effect looks best in WebKit browsers. Unfortunately, Firefox does not play along very nicely.
另请注意,在WebKit浏览器中3D效果看起来最好。 不幸的是,Firefox不能很好地配合使用。
Let’s get started!
让我们开始吧!
标记 (The Markup)
Let’s create an unordered list which will have the class “timeline”. We’ll add several list items with the class “event”. Each event will have a radio input, an empty label, a thumbnail and a container for the content. This container will have perspective, so we’ll give it the class “content-perspective”. Note that the radio inputs all have the same name. That’s how we indicate that they all belong to the same group and we can only select one at a time.
让我们创建一个具有“时间轴”类的无序列表。 我们将添加几个带有“事件”类的列表项。 每个事件都会有一个单选输入,一个空标签,一个缩略图和一个内容容器。 该容器将具有透视图,因此我们将其赋予“内容透视”类。 请注意,所有无线电输入都具有相同的名称。 这样一来,我们便表示它们都属于同一组,并且一次只能选择一个。
<ul class="timeline">
<li class="event">
<input type="radio" name="tl-group" checked/>
<label></label>
<div class="thumb user-4"><span>19 Nov</span></div>
<div class="content-perspective">
<div class="content">
<div class="content-inner">
<h3>I find your lack of faith disturbing</h3>
<p>Some text</p>
</div>
</div>
</div>
</li>
<li class="event">
<input type="radio" name="tl-group"/>
<!-- ... -->
</li>
<!-- ... -->
</ul>
The thumbnail will have the class “thumb” and and additional class for the user which will be user-1 to user-8 in our case. The span will be used to add the date.
缩略图将具有类别“ thumb”和用于用户的其他类别,在本例中为用户1到用户8。 该跨度将用于添加日期。
CSS (The CSS)
Note that the CSS will not contain any vendor prefixes, but you will find them in the files. We’ll be using the border-box box-sizing and reset all the paddings and margins with the following snippet:
请注意,CSS将不包含任何供应商前缀,但是您可以在文件中找到它们。 我们将使用border-box box-sizing,并使用以下代码段重置所有填充和边距:
*,
*:after,
*:before {
box-sizing: border-box;
padding: 0;
margin: 0;
}
You can of course omit the resetting of the paddings and margins, just keep in mind to adjust them when we define the style of the following elements.
当然,您可以省略对边距和边距的重置,只要在定义以下元素的样式时记住要调整它们即可。
Let’s first add a font that we’ve created with fontello.com. This font will only have four characters, two arrows and two states that we’ll associate with being “checked” and “unchecked”. It’s part of the Font Awesome font:
让我们首先添加我们使用fontello.com创建的字体。 该字体只有四个字符,两个箭头和两个状态,我们将它们与“选中”和“未选中”相关联。 它是Font Awesome字体的一部分:
@font-face {
font-family: 'fontawesome-selected';
src: url("font/fontawesome-selected.eot");
src:
url("font/fontawesome-selected.eot?#iefix") format('embedded-opentype'),
url("font/fontawesome-selected.woff") format('woff'),
url("font/fontawesome-selected.ttf") format('truetype'),
url("font/fontawesome-selected.svg#fontawesome-selected") format('svg');
font-weight: normal;
font-style: normal;
}
Let’s set the timeline to be relative and add some padding to it:
让我们将时间轴设置为相对的,并为其添加一些填充:
.timeline {
position: relative;
padding: 30px 0 50px 0;
font-family: 'Gorditas', Arial, sans-serif;
}
The white striped line will be created by using a pseudo element. It’s position will be absolute and we’ll give it a striped background (made with Patternify):
白色条纹线将通过使用伪元素创建。 它的位置是绝对的,我们将给它一个带条纹的背景(由Patternify制作):
.timeline:before {
content: '';
position: absolute;
width: 5px;
height: 100%;
top: 0;
left: 165px;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAJUlEQVQIW2NkQAP/gYARWQwkAOLDBeECjEAAkkEWAKtEFwAJAgAZEBP+oM1IkwAAAABJRU5ErkJggg==);
}
Each event will be of position relative and we’ll add some margin to the bottom to have some space between the items and some padding to the right. This will make sure that the list item does not overflow too much to the right when we apply the 3D rotation:
每个事件的位置都是相对的,我们将在底部添加一些边距,以在各项之间留出一定的空间,并在右侧留出一些填充。 这将确保当我们应用3D旋转时,列表项不会在右侧溢出太多:
.event {
position: relative;
margin-bottom: 80px;
padding-right: 40px;
}
Let’s take care of the left side for now. Here we want the thumbnail to show up, so we’ll set it to absolute, make it round by setting the border-radius to 50%:
现在让我们来照顾左边。 这里我们要显示缩略图,因此我们将其设置为绝对,通过将border-radius设置为50%使其变圆:
.thumb {
position: absolute;
width: 100px;
height: 100px;
box-shadow:
0 0 0 8px rgba(65,131,142,1),
0 1px 1px rgba(255,255,255,0.5);
background-repeat: no-repeat;
border-radius: 50%;
transform: scale(0.8) translateX(24px);
}
We’ll also add a transformation which will scale it down a bit. Our aim is to scale it up, once the corresponding radio button is selected. Since the scaling will move the element a bit, we could either set the transform origin or translate it on the X-axis. We’ll go for the latter one and translate it 24 pixels. Why do we move it at all? We’ll have a little pseudo-element with a zig zag background that will point from the thumbnail and we want it to “touch” the radio input:
我们还将添加一个转换,将其缩小一点。 我们的目标是在选择了相应的单选按钮后将其放大。 由于缩放会稍微移动元素,因此我们可以设置转换原点或在X轴上平移它。 我们将选择后者,并将其转换为24像素。 我们为什么要移动它? 我们将有一个带有锯齿形背景的伪元素,它将从缩略图指向,我们希望它“触摸”单选输入:
.thumb:before {
content: '';
position: absolute;
height: 8px;
z-index: -1;
background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAYAAADA+m62AAAAPElEQVQYV2NkQAP/gYARCNDFUQRgirAphitEl0TngxXisg5ZnBGXIpgbYfIYjkb3BNxGbBLYxIgyEaQRAA8KKAWYsZtjAAAAAElFTkSuQmCC);
width: 51px;
top: 42px;
left: 100%;
margin-left: 8px;
}
Let’s style the span for the date. We’ll set it to position absolute and put it under the thumbnail:
让我们设置日期范围的样式。 我们将其设置为绝对位置并将其放在缩略图下方:
.thumb span {
color: #41838e;
width: 100%;
text-align: center;
font-weight: 700;
font-size: 15px;
text-transform: uppercase;
position: absolute;
bottom: -30px;
}
Now, let’s define the different background images for the users (we could also use images instead of background-images):
现在,让我们为用户定义不同的背景图像(我们也可以使用图像代替背景图像):
.user-1 {
background-image: url(../images/chewbacca.jpg);
}
.user-2 {
background-image: url(../images/barf.jpg);
}
.user-3 {
background-image: url(../images/darkhelmet.jpg);
}
.user-4 {
background-image: url(../images/darthvader.jpg);
}
.user-5 {
background-image: url(../images/leia.jpg);
}
.user-6 {
background-image: url(../images/vespa.jpg);
}
.user-7 {
background-image: url(../images/c3po.jpg);
}
.user-8 {
background-image: url(../images/dotmatrix.jpg);
}
For the radio input we’ll use a little trick. We want our transitions and changes to trigger when we click on an input since we can make use of the “checked” state and the sibling selectors. There are many different ways to do it, one of them involving clicking on the label which selects the input in most of the browsers automatically. But we don’t want to use that hack, instead we’ll just use the input itself by stacking it on top of the label and making it transparent. What appears to be a click on the label is actually a click on the input. The label is just used for fanciness in this case, and if we could add pseudo-elements to inputs, we wouldn’t need it at all.
对于无线电输入,我们将使用一些技巧。 我们希望在单击输入时触发转换和更改,因为我们可以利用“ checked”状态和同级选择器。 有许多不同的方法可以执行此操作,其中一种方法是单击标签,该标签会自动在大多数浏览器中选择输入。 但是,我们不想使用该技巧,相反,我们只是将输入本身堆叠在标签上方并使其透明即可使用输入。 似乎是在标签上单击,实际上是在输入上单击。 在这种情况下,标签只是出于幻想,如果我们可以向输入中添加伪元素,则根本不需要它。
So, what we do is to give both, the label and the input the same width and height and put them in the same place:
因此,我们要做的是使标签和输入的宽度和高度相同,并将它们放置在同一位置:
.event label,
.event input[type="radio"] {
width: 24px;
height: 24px;
left: 158px;
top: 36px;
position: absolute;
display: block;
}
Since the input needs to precede all the other elements (remember that we want to reach them via the sibling selector), we’ll give it a higher z-index, to be on top of the label. If we wouldn’t do that, the label would naturally be on top of it:
由于输入需要在所有其他元素之前(请记住,我们想通过同级选择器到达它们),因此我们将在标签上方给它更高的z索引。 如果我们不这样做,标签自然会在它上面:
.event input[type="radio"] {
opacity: 0;
z-index: 10;
cursor: pointer;
}
Now, let’s create a little pseudo-element for the label, one that will contain a little icon from the icon font that we’ve included before. We’ll pull it into place and position it absolutely:
现在,让我们为标签创建一个伪元素,其中将包含之前包含的图标字体中的一个小图标。 我们将其拉入到位并绝对定位:
.event label:after {
font-family: 'fontawesome-selected';
content: 'e702';
background: #fff;
border-radius: 50%;
color: #41838E;
font-size: 26px;
height: 100%;
width: 100%;
left: -2px;
top: -3px;
line-height: 24px;
position: absolute;
text-align: center;
}
Let’s move on to the right side of our timeline, the content. We need a wrapper with perspective which will have a left margin that will move it to the side:
让我们转到时间线的右侧,即内容。 我们需要一个具有透视图的包装器,该包装器的左边缘将其移至一侧:
.content-perspective {
margin-left: 230px;
position: relative;
perspective: 600px;
}
To “connect” the content part with the input, we’ll use a pseudo element that will look like a line:
要将内容部分与输入“连接”,我们将使用一个看起来像一条线的伪元素:
.content-perspective:before {
content: '';
width: 37px;
left: -51px;
top: 45px;
position: absolute;
height: 1px;
z-index: -1;
background: #fff;
}
It’s important that we set the pseudo-class to the perspective wrapper since we don’t want it to rotate like anything else inside.
将伪类设置为透视包装很重要,因为我们不希望它像内部其他任何东西一样旋转。
So, the content will be rotated 10 degrees, which, together with a transform origin set to the outer top and left, will make it appear as if its right side is being pushed back:
因此,内容将旋转10度,再加上设置在外部顶部和左侧的转换原点,将使其看起来好像被向右推后一样:
.content {
transform: rotateY(10deg);
transform-origin: 0 0;
transform-style: preserve-3d;
}
The inner content will actually have the background color and the box shadow. We need this extra element in order to avoid funny things happening:
内部内容实际上将具有背景色和框阴影。 我们需要这个额外的元素,以避免发生有趣的事情:
.content-inner {
position: relative;
padding: 20px;
color: #333;
border-left: 5px solid #41838e;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
background: #fff;
}
Let’s make the h3 look pretty:
让我们让h3看起来很漂亮:
.content-inner h3 {
font-size: 26px;
padding: 5px 0 5px 0;
color: #41838e;
}
The paragraph will have a special role since it will transition it’s height. Because we can’t animate to height: auto (which would be optimal since we don’t know the height of the element), we’ll need to use a smart hack suggested by Lea Verou. We’ll animate the max-height value instead. Note that this does only create the desired transition if the max-height value is very close to the real height itself. So, we’ll need to adjust the max-height to not be too big. This is not a good solution, especially in a responsive setting. You’ll of course not want to worry about that since it almost breaks down to the same problem as setting the height itself. For our experiment we don’t want to use any JavaScript, so this is the best option and we’ll also define some media queries, so that things don’t break.
该段将扮演特殊角色,因为它将转换其高度。 因为我们无法为height:auto设置动画(这是最佳选择,因为我们不知道元素的高度),所以我们需要使用Lea Verou建议的智能技巧。 我们将为max-height值设置动画。 请注意,仅当max-height值非常接近实际高度本身时,这才创建所需的过渡。 因此,我们需要将最大高度调整为不太大。 这不是一个好的解决方案,尤其是在响应式设置中。 您当然不必担心,因为它几乎可以分解为与设置高度本身相同的问题。 对于我们的实验,我们不想使用任何JavaScript,因此这是最好的选择,并且我们还将定义一些媒体查询,以使事情不会中断。
So, we set the max-height to 0 pixel and give the paragraph a transparent color (playing with opacities here was causing some flickering, so we’ll use this trick instead):
因此,我们将max-height设置为0像素,并为段落提供了透明的颜色(在此玩弄不透明会引起闪烁,因此我们将使用此技巧):
.content-inner p {
font-size: 1.8px;
max-height: 0px;
overflow: hidden;
color: rgba(0,0,0,0);
text-align: left;
}
The aim is to expand the paragraph and animate the alpha opacity value of the RGBA to 1, simulating a fading in.
目的是扩展段落并将RGBA的alpha不透明度值设置为1,以模拟淡入。
Let’s add the little arrow to the left side, using the icon font:
让我们使用图标字体在左侧添加小箭头:
.content-inner:before {
font-family: 'fontawesome-selected';
content: '25c2';
font-weight: normal;
font-size: 54px;
line-height: 54px;
position: absolute;
width: 30px;
height: 30px;
color: #41838e;
left: -22px;
top: 19px;
z-index: -1;
}
Next, we’ll define all the different transitions for the respective elements. We first want the content to expand and after that we want all the other transitions to happen. So we’ll need to add some delay for the resting transitions.
接下来,我们将为各个元素定义所有不同的过渡。 我们首先要扩展内容,然后再进行所有其他过渡。 因此,我们需要为静态过渡添加一些延迟。
Let’s give the thumbnail, its span and the headline of the content a transition with 0.2s delay:
让我们为缩略图,其跨度和内容的标题提供0.2秒延迟的过渡:
.thumb,
.thumb span,
.content-inner h3 {
transition: all 0.6s ease-in-out 0.2s;
}
The inner content needs a transition for the box shadow:
内部内容需要为框阴影过渡:
.content-inner {
transition: box-shadow 0.8s linear 0.2s;
}
The content will have a transition for the transformation and we’ll add a nice smooth bounce as the timing function:
内容将进行转换,我们将添加一个平滑的跳动作为计时函数:
.content {
transition: transform 0.8s cubic-bezier(.59,1.45,.69,.98) 0.2s;
}
Now, the paragraph needs a transition for the max-height and for the color.
现在,该段落需要过渡到最大高度和颜色。
.content-inner p {
transition: max-height 0.5s linear, color 0.3s linear;
}
Next, we’ll define what happens when we select a radio input. Once a radio is “checked”, we’ll give a different icon to the label’s pseudo class and change its color and box shadow:
接下来,我们将定义选择单选输入时会发生的情况。 选中“广播”后,我们将为标签的伪类提供其他图标,并更改其颜色和阴影:
.event input[type="radio"]:checked + label:after {
content: '2714';
color: #F26328;
box-shadow: 0 0 0 5px rgba(255, 255, 255, 0.8);
}
Let’s also change the color of the line and the heading of the content:
我们还要更改线条的颜色和内容的标题:
.event input[type="radio"]:checked ~ .content-perspective:before {
background: #F26328;
}
.event input[type="radio"]:checked ~ .content-perspective .content-inner h3 {
color: #F26328;
}
The content will rotate to the front:
内容将旋转到最前面:
.event input[type="radio"]:checked ~ .content-perspective .content {
transform: rotateY(-5deg);
}
And the inner content will get a different border color and box shadow:
内部内容将具有不同的边框颜色和框阴影:
.event input[type="radio"]:checked ~ .content-perspective .content-inner {
border-color: #F26328;
box-shadow: 10px 0px 10px -6px rgba(0, 0, 0, 0.1);
}
The paragraph will animate to a max-height of 260 pixels and the color will become more opaque:
段落将动画为最大高度为260像素,并且颜色将变得更加不透明:
.event input[type="radio"]:checked ~ .content-perspective .content-inner p {
max-height: 260px; /* Add media queries */
color: rgba(0,0,0,0.6);
transition-delay: 0s, 0.6s;
}
The transition delay set here will ensure that the color transition will happen with a delay. We first want the max-height to animate and then then the transparency of the RGBA color.
此处设置的过渡延迟将确保颜色过渡会延迟发生。 我们首先要设置最大高度的动画,然后再设置RGBA颜色的透明度。
Let’s change the color of the arrow:
让我们更改箭头的颜色:
.event input[type="radio"]:checked ~ .content-perspective .content-inner:before {
color: #F26328;
}
The thumbnail will scale up (and translate back to 0px) and we’ll give it a border-like box shadow:
缩略图将放大(并转换回0px),我们将给它一个类似边框的盒子阴影:
.event input[type="radio"]:checked ~ .thumb {
transform: scale(1);
box-shadow:
0 0 0 8px rgba(242,99,40,1),
0 1px 1px rgba(255,255,255,0.5);
}
The date will change color:
日期将更改颜色:
.event input[type="radio"]:checked ~ .thumb span {
color: #F26328;
}
And we’ll replace the blue zig zag line with an orange one:
然后,我们将橙色的蓝色字形线替换为橙色的:
.event input[type="radio"]:checked ~ .thumb:before {
background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAYAAADA+m62AAAAPUlEQVQYV2NkQAOfUjX+882+wYgujiIAU4RNMVwhuiQ6H6wQl3XI4oy4FMHcCJPHcDS6J2A2EqUQpJhohQBbNyaHFmzEqgAAAABJRU5ErkJggg==);
}
Now we need to make sure that everything looks fine when we view this with smaller screens. At 850 pixel we’ll set the font sizes to be smaller and also reset the max-height of the paragraph:
现在,我们需要确保在较小的屏幕上观看时,一切看起来都很好。 在850像素处,我们将字体设置为较小,并重置该段落的最大高度:
@media screen and (max-width: 850px) {
.content-inner h3 {
font-size: 20px;
}
.content-inner p {
font-size: 14px;
text-align: justify;
}
.event input[type="radio"]:checked ~ .content-perspective .content-inner p {
max-height: 500px;
}
}
From 540 pixel we need to do some more stuff. Since everything is getting a bit crammed, we will change out layout a bit. The thumbnail will stay where it is, but the content will be moved from he right side to under the thumbnail. The input will be placed on top of the thumbnail, so that we need to click the there in order to open the content. We’ll get rid of some elements that we won’t need here anymore, like the label and the lines. We’ll also change the rotation of the content making it stick out at the bottom and not the right side:
从540像素开始,我们需要做更多的工作。 由于一切都变得塞满了,我们将稍微改变布局。 缩略图将保留在原处,但是内容将从右侧移到缩略图下方。 输入将放置在缩略图的顶部,因此我们需要单击缩略图以打开内容。 我们将摆脱一些不再需要的元素,例如标签和线条。 我们还将更改内容的旋转方式,使内容突出显示在底部而不是右侧:
@media screen and (max-width: 540px) {
.timeline:before {
left: 50px;
}
.event {
padding-right: 0px;
margin-bottom: 100px;
}
.thumb {
transform: scale(0.8);
}
.event input[type="radio"] {
width: 100px;
height: 100px;
left: 0px;
top: 0px;
}
.thumb:before,
.event input[type="radio"]:checked ~ .thumb:before {
background: none;
width: 0;
}
.event label {
display: none;
}
.content-perspective {
margin-left: 0px;
top: 80px;
}
.content-perspective:before {
height: 0px;
}
.content {
transform: rotateX(-10deg);
}
.event input[type="radio"]:checked ~ .content-perspective .content {
transform: rotateX(10deg);
}
.content-inner {
border-left: none;
border-top: 5px solid #41838e;
}
.event input[type="radio"]:checked ~ .content-perspective .content-inner {
border-color: #F26328;
box-shadow: 0 10px 10px -6px rgba(0, 0, 0, 0.1);
}
.content-inner:before {
content: '25b4';
left: 33px;
top: -32px;
}
.event input[type="radio"]:checked ~ .content-perspective .content-inner p {
max-height: 300px;
}
}
The max-height value needs to be adjusted again because now we’ll have a bit more horizontal space for the paragraph.
需要再次调整max-height值,因为现在段落的水平空间会更多一些。
If you’d like it to be possible to open more than one content item, just try out using checkboxes instead of radio inputs.
如果您希望可以打开多个内容项,只需尝试使用复选框而不是单选输入即可。
And that’s it! I hope you enjoyed this tutorial and find it inspiring!
就是这样! 希望您喜欢本教程并从中获得启发!
翻译自: https://tympanus.net/codrops/2012/11/19/responsive-css-timeline-with-3d-effect/