Welcome to my public account: front-end detective
Not long ago, in this article: CSS with :has pseudo-classes can be used to introduce some usage scenarios of :has
pseudo-classes, which can be said to greatly subvert the cognition of CSS selectors and make a lot of cumbersome The js
logic is easily implemented by the flexible CSS
. This time I bring a more common case, a 3d carousel , like this
This carousel has several points that need to be implemented:
- 3d vision, that is, the middle is big and the sides are small
- Automatic rotation, automatic pause when the mouse is placed
- Clicking on any card will immediately jump to that card
This time, I use :has
to achieve such a function. I believe it can bring different ideas. Let's take a look.
⚠️Warm reminder: Chrome 101+ is required for compatibility, and experimental features are started (105+ is officially supported), Safari 15.4+, Firefox officially says that the experimental features can be supported, but the actual measurement does not support
1. 3d visual style
This part is the point.
First, let's make a simple layout. Suppose HTML
is like this:
<div class="view" id="view">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item current">5</div>
<div class="item">6</div>
<div class="item">7</div>
<div class="item">8</div>
<div class="item">9</div>
<div class="item">10</div>
</div>
To reduce complexity as much as possible, we can focus all the changes on a single class name.
For example .current
means the currently selected item, which is the middle item. Here, the absolute positioning method is used to stack all the card items together, and then set the 3d
view to move the Z
axis of the card back a certain distance, so that the .current
--In the front .current
all cards, achieve the effect of near big and far small, the specific implementation is as follows
.view {
position: relative;
width: 400px;
height: 250px;
transform-style: preserve-3d;
perspective: 500px;
}
.item {
position: absolute;
width: 100%;
height: 100%;
border-radius: 8px;
display: grid;
place-content: center;
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1);
transform: translate3d(0, 0, -100px);
}
.item.current {
transform: translate3d(0, 0, 0);
}
The effect is as follows
In fact, the hierarchy is like this, which can be seen in the Chrome layer
Now, we need to let the adjacent left and right sides leak out, the right one is easier, just use the adjacent sibling selector +
.item.current + .item{
transform: translate3d(30%, 0, -100px);
}
What about the adjacent left? It used to be unsolvable, and it was very troublesome to set different class names through JS, but now there is a :has
pseudo-class, which can also be easily implemented, as follows
.item:has(+.item.current){
transform: translate3d(-30%, 0, -100px);
}
The effect is as follows
However, there are still some critical situations, such as in the first card, because there is no sibling node in front, so it becomes like this
So you need to put the last element on the left side of the first element, the first element is :first-child
, and the last element is :last-child
, so the implementation is like this (The last element is processed in the same way)
.item.current:first-child ~ .item:last-child {
transform: translate3d(-30%, 0, -100px);
opacity: 1;
}
.item:first-child:has(~ .item.current:last-child) {
transform: translate3d(30%, 0, -100px);
opacity: 1;
}
This takes care of the edge cases
Further, two cards can also be exposed to both sides, and the implementation is similar. The complete implementation is as follows
/*当前项*/
.item.current {
transform: translate3d(0, 0, 0);
}
/*当前项右1*/
.item.current + .item,
.item:first-child:has(~ .item.current:last-child) {
transform: translate3d(30%, 0, -100px);
}
/*当前项左1*/
.item:has(+ .item.current),
.item.current:first-child ~ .item:last-child {
transform: translate3d(-30%, 0, -100px);
}
/*当前项右2*/
.item.current + .item + .item,
.item:first-child:has(~ .item.current:nth-last-child(2)),
.item:nth-child(2):has(~ .item.current:last-child) {
transform: translate3d(50%, 0, -150px);
}
/*当前项左2*/
.item:has(+.item + .item.current),
.item.current:first-child ~ .item:nth-last-child(2),
.item.current:nth-child(2) ~ .item:last-child{
transform: translate3d(-50%, 0, -150px);
}
In this way, all styles of .current
are processed, and all changes are controlled with only one variable
2. Automatic rotation and pause
With the above processing, the next logic is very simple, just need to pass the change of js
dynamic control .current
.
In general, we may think of using a timer setInterval
, but here, we can also do not use a timer. With the power of CSS animation, we can more easily complete such an interaction.
If you are interested, you can refer to the previous article: Are you still using a timer? Use CSS to monitor events , I believe it can bring you some inspiration
The thing to do is simple, add an insignificant CSS animation to the container
.view {
/**/
animation: scroll 3s infinite;
}
@keyframes scroll {
to {
transform: translateZ(.1px); /*无关紧要的动画样式*/
}
}
In this way, an animation with a duration of 3s
and an infinite loop is obtained.
Then, listen for the animationiteration
event. This event will be called back every time the animation runs. Here, it runs every 3s
, just like setInterval
same function
GlobalEventHandlers.onanimationiteration - Web API Interface Reference | MDN (mozilla.org)
Process .current
in the animationiteration
callback. The logic is very simple, remove the current .current
, and add .current
to the next boundary. That's it, the specific implementation is as follows
view.addEventListener("animationiteration", () => {
const current = view.querySelector(".current") || view.firstElementChild;
current.classList.remove("current");
if (current.nextElementSibling) {
current.nextElementSibling.classList.add("current");
} else {
view.firstElementChild.classList.add("current");
}
});
The biggest advantage of using animationiteration
is that the animation can be controlled directly through CSS, and there is no need to monitor mouse movement events.
.view:hover{
animation-play-state: paused;
}
The effect is as follows (convenient for demonstration, the speed is increased)
3. Click to switch quickly
Click to switch, my first thought is to pass :checked
, which is similar to radio, such as
<div class="view" id="view">
<label class="item"><input type="radio" checked name="item"></label>
<label class="item"><input type="radio" name="item"></label>
<label class="item"><input type="radio" name="item"></label>
<label class="item"><input type="radio" name="item"></label>
<label class="item"><input type="radio" name="item"></label>
<label class="item"><input type="radio" name="item"></label>
</div>
But at present :has
pseudo-class does not seem to support multi-level nesting , such as the following statement
.item:has(+.item:has(:checked)){
/*不生效*/
}
.item:has(:checked)
The selected parent is the selected parent of the child element, and then .item:has(+.item:has(:checked))
means that the previous sibling node is selected, so that the selection function can be realized, but unfortunately it is not supported now 😌( may be supported in the future)
There is no way, it can only be achieved by traditional methods, directly binding to monitor click
event
view.addEventListener("click", (ev) => {
const current = view.querySelector(".current") || view.firstElementChild;
current.classList.remove("current");
ev.target.closest('.item').classList.add("current");
});
The effect is as follows
The complete code can be viewed online demo: CSS 3dscroll(runjs.work)
Fourth, to summarize
The above is to use the :has
pseudo-class to realize all the details of a 3d carousel. All visual changes are done in CSS, and JS only needs to deal with the switching logic. The above is more concise and elegant, and the following summarizes
- 3d visual style can be achieved by
transform-style: preserve-3d;
to achieve the effect of near big and far small - By
.item:has(+.item.current)
can set the sibling node in front of the current item - The first and last two critical cases also need to be considered
- The automatic rotation and pause of the carousel image can be called back by
animationiteration
. The advantage of this method is that it can be controlled by:hover
-
:has
Pseudo-class does not seem to support multi-level nesting , I hope it can be supported in the future~
:has
very powerful, the only flaw so far is compatibility. Fortunately, browsers are more active in following up this new feature, and it can be used in internal projects by this time next year. Finally, if you think it's good and helpful to you, please like, bookmark, and forward ❤❤❤
Welcome to my public account: front-end detective
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。