关于前端:借助-has-实现3d轮播图

2次阅读

共计 4577 个字符,预计需要花费 12 分钟才能阅读完成。

欢送关注我的公众号:前端侦探

前不久在这篇文章:CSS 有了:has 伪类能够做些什么 中介绍了:has 伪类的一些应用场景,能够说大大颠覆了 CSS 选择器的认知,让很多繁琐的 js逻辑通过灵便的 CSS 轻易实现了。这次带来一个比拟常见的案例,3d 轮播图,就像这样的

这个轮播图有几个须要实现的点:

  1. 3d 视觉,也就是两头大,两边小
  2. 主动轮播,鼠标放上主动暂停
  3. 点击任意卡片会立刻跳转到该卡片

这次借助 :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 只须要解决切换逻辑就行了,相比以前而言,实现上更加简洁和优雅,上面总结一下

  1. 3d 视觉款式能够通过 transform-style: preserve-3d; 实现近大远小的成果
  2. 通过 .item:has(+.item.current) 能够设置以后项后面的兄弟节点
  3. 还须要思考第一个和最初一个这两种临界状况
  4. 轮播图主动轮播和暂停能够借助 animationiteration 回调,这种形式的劣势是能够通过 :hover 管制
  5. :has伪类貌似 不反对多层嵌套,心愿当前能够反对吧~

:has十分弱小,目前惟一的缺点在于兼容性。好在浏览器对于这一新个性跟进的都比拟踊跃,明年这个时候差不多能够在外部我的项目用起来了。最初,如果感觉还不错,对你有帮忙的话,欢送点赞、珍藏、转发❤❤❤

欢送关注我的公众号:前端侦探

正文完
 0