欢送关注我的公众号:前端侦探
前不久在这篇文章: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
十分弱小,目前惟一的缺点在于兼容性。好在浏览器对于这一新个性跟进的都比拟踊跃,明年这个时候差不多能够在外部我的项目用起来了。最初,如果感觉还不错,对你有帮忙的话,欢送点赞、珍藏、转发❤❤❤
欢送关注我的公众号:前端侦探