共计 4577 个字符,预计需要花费 12 分钟才能阅读完成。
欢送关注我的公众号:前端侦探
前不久在这篇文章:CSS 有了:has 伪类能够做些什么 中介绍了:has
伪类的一些应用场景,能够说大大颠覆了 CSS 选择器的认知,让很多繁琐的 js
逻辑通过灵便的 CSS
轻易实现了。这次带来一个比拟常见的案例,3d 轮播图,就像这样的
这个轮播图有几个须要实现的点:
- 3d 视觉,也就是两头大,两边小
- 主动轮播,鼠标放上主动暂停
- 点击任意卡片会立刻跳转到该卡片
这次借助 :has
来实现这样的性能,置信能够带来不一样的思路,一起看看吧
⚠️舒适揭示:兼容性要求须要 Chrome 101+,并且开始试验个性(105+ 正式反对),Safari 15.4+,Firefox 官网说开启试验个性能够反对,然而实测并不反对
一、3d 视觉款式
这一部分才是重点。
首先咱们来简略布局一下,假如 HTML
是这样的:
<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>
为了尽可能减少复杂度,咱们能够将所有的变动都集中在一个类名上。
比方 .current
示意以后选中项,也就是两头的那项。这里采纳相对定位的形式,将所有卡片项都重叠在一起,而后设置 3d
视图,将卡片的 Z
轴往后挪动一段距离,让 .current
在所有卡片的最后面,实现近大远小的成果,具体实现如下
.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);
}
成果如下
其实层级是这样的,能够在 Chrome 图层中看到
当初,咱们须要让相邻左右两边的都漏出来,左边的比拟容易,用相邻兄弟选择器 +
就能够了
.item.current + .item{transform: translate3d(30%, 0, -100px);
}
那相邻右边的呢?以前是无解的,只能通过 JS 别离设置不同的类名,十分麻烦,但当初有了 :has
伪类,也能够轻松实现了,如下
.item:has(+.item.current){transform: translate3d(-30%, 0, -100px);
}
成果如下
不过还有一些临界状况,比方在第一个卡片时,因为后面没有兄弟节点了,所以就成了这样
所以须要再在第一个元素时,把最初一个元素放在它的右边,第一个元素是:first-child
,最初一个元素是:last-child
,所以实现是这样的(最初一个元素解决同理)
.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;
}
这样就解决好了边界状况
进一步,还能够向两侧露出两个卡片,实现也是相似的,残缺实现如下
/* 以后项 */
.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);
}
这样就实现了对于 .current
的全副款式解决了,只用一个变量就管制了所有变动
二、主动轮播和暂停
有了下面的解决,接下来的逻辑就非常简单了,只须要通过 js
动态控制 .current
的变动就行了。
个别状况下,咱们可能会想到用定时器setInterval
,但这里,咱们也能够不应用定时器,借助 CSS 动画的力量,能够更轻松地残缺这样的交互。
有趣味的能够参考之前这篇文章:还在用定时器吗?借助 CSS 来监听事件,置信能够给你带来一些灵感
要做的事件很简略,给容器增加一个无关紧要的 CSS 动画
.view {
/**/
animation: scroll 3s infinite;
}
@keyframes scroll {
to {transform: translateZ(.1px); /* 无关紧要的动画款式 */
}
}
这样就失去了一个时长为3s
,有限循环的动画了。
而后,监听 animationiteration
事件,这个事件在动画每次运行一次就会回调一次,在这里也就是每 3s
运行一次,就像 setInterval
的性能一样
GlobalEventHandlers.onanimationiteration – Web API 接口参考 | MDN (mozilla.org)
在 animationiteration
回调中解决 .current
逻辑,很简略,移除以后的.current
,给下一个增加.current
,留神一下边界就行了,具体实现如下
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");
}
});
应用 animationiteration
的最大益处是,能够间接通过 CSS 进行动画的管制,再也无需监听鼠标移入移出事件了
.view:hover{animation-play-state: paused;}
成果如下(不便演示,速度调快了)
三、点击疾速切换
点击切换,我其实最先想到的是通过:checked
,也就是相似单选,比方
<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>
然而目前来看 :has
伪类貌似 不反对多层嵌套,比方上面这条语句
.item:has(+.item:has(:checked)){/* 不失效 */}
.item:has(:checked)
选中的是子元素被选中的父级,而后 .item:has(+.item:has(:checked))
示意选中它的后面一个兄弟节点,这样就能实现选中性能了,惋惜当初并不反对😌(当前可能反对)
没方法,只能通过传统形式来实现,间接绑定监听 click
事件
view.addEventListener("click", (ev) => {const current = view.querySelector(".current") || view.firstElementChild;
current.classList.remove("current");
ev.target.closest('.item').classList.add("current");
});
成果如下
残缺代码能够查看线上 demo:CSS 3dscroll(runjs.work)
四、总结一下
以上就是借助 :has
伪类来实现一个 3d 轮播图的全副细节了,所有的视觉变动全副在 CSS 中实现,JS 只须要解决切换逻辑就行了,相比以前而言,实现上更加简洁和优雅,上面总结一下
- 3d 视觉款式能够通过
transform-style: preserve-3d;
实现近大远小的成果 - 通过
.item:has(+.item.current)
能够设置以后项后面的兄弟节点 - 还须要思考第一个和最初一个这两种临界状况
- 轮播图主动轮播和暂停能够借助
animationiteration
回调,这种形式的劣势是能够通过:hover
管制 :has
伪类貌似 不反对多层嵌套,心愿当前能够反对吧~
:has
十分弱小,目前惟一的缺点在于兼容性。好在浏览器对于这一新个性跟进的都比拟踊跃,明年这个时候差不多能够在外部我的项目用起来了。最初,如果感觉还不错,对你有帮忙的话,欢送点赞、珍藏、转发❤❤❤
欢送关注我的公众号:前端侦探