你好。今天,我使用酷CS和JS功能进行简单的幻灯片。
我将使用:
- CSS变量
- CSS转换
- CSS功能
- CSS网格
- JS代理
HTML
<div class="slideshow">
<div class="slides-container">
<div class="slides">
<img class="slide" src="https://picsum.photos/600/300?v=1" alt="">
<img class="slide" src="https://picsum.photos/600/300?v=2" alt="">
<img class="slide" src="https://picsum.photos/600/300?v=3" alt="">
<img class="slide" src="https://picsum.photos/600/300?v=4" alt="">
<img class="slide" src="https://picsum.photos/600/300?v=5" alt="">
</div>
</div>
<div class="bullets">
</div>
<div class="buttons">
<button type="button" class="prev">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
</svg>
</button>
<button type="button" class="next">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
</svg>
</button>
</div>
</div>
让我们分解。
我们有一个div,其中包含一类.slideshow
的整个幻灯片。这个容器有3个直接的孩子。第一个是.slides-container
,它将充当我们幻灯片将显示的“窗口”。然后是一个空的.bullets
容器,我们将根据幻灯片的数量动态放置子弹。最后一个是.buttons
容器,带有.prev
和.next
按钮。
在.slides-container
内部有另一个带有一类.slides
的容器,可容纳我们所有的幻灯片。该DIV将在我们的“窗口”后方向右移动,以揭示活动幻灯片。
对于图像,我使用了https://picsum.photos的图片,对于.prev
和.next
按钮,我使用了https://heroicons.com的一些图标。
CSS
首先,让我们定义一些CSS变量,这些变量将稍后使用。
:root {
--max-size: 600px;
--size: min(var(--max-size), 100vw);
--active: 0;
}
--max-size
是每张幻灯片的最大尺寸。
--size
是整个幻灯片的计算大小。它的屏幕比--max-size
小100VW,而--max-size
对于较大的屏幕。
--active
变量保存了活动幻灯片的索引。
现在,外部容器将是带有1列和2行的CSS网格。第一行将包含幻灯片和按钮,第二行将包含子弹。
.slideshow {
display: grid;
grid-template-rows: 1fr auto;
grid-template-columns: 1fr;
gap: 10px;
width: var(--size);
}
然后,我们的“窗口”。 .slides-container
将位于父格的第1列和第1列中。它将通过具有透明背景并隐藏任何溢出的东西来创建一个孔(窗口)。
.slides-container {
grid-column: 1;
grid-row: 1;
overflow: hidden;
}
然后是.slides
。这是一个包含我们所有幻灯片的Div。它具有display: flex
,可以连续将所有内容放入所有内容,并将四处移动以揭示活动幻灯片。这里有趣的部分是基于活动幻灯片和幻灯片大小的容器位置的计算。
是-1 * size * active
。假设大小为600px。当活动幻灯片是0时,它将为我们在X轴上给我们0移动。当活动是第一个时,它将给予-1 * 600px * 1 = -600px
,因此它将向左移动600px。
.slides {
display: flex;
transform: translateX(calc(-1 * var(--size) * var(--active)));
transition: transform 0.3s ease;
}
我们的图像需要具有等于--size
变量的宽度,才能得到响应。这是因为他们的父母真的很长,我们不能依靠图像占用所有可用宽度并具有自动的高度。
img {
width: var(--size);
}
然后是按钮容器。这个Div必须坐在幻灯片的顶部。我不喜欢使用position: absolute
,因此我使用了将多个元素放在同一网格单元格中的网格功能。因此,我将其放在与.slides-container
的网格单元中。问题是我无法单击幻灯片。如果幻灯片具有链接,我将无法单击它,因为另一个DIV在其前面。为了解决这个问题,我在按钮容器中添加了pointer-events: none
。现在我无法单击按钮。所以我在按钮上添加pointer-events: all
。
该容器中的按钮将在垂直方向和水平侧面。
.buttons {
z-index: 1;
display: flex;
justify-content: space-between; /* Horizontally at the sides */
align-items: center; /* Vertically in the middle */
padding: 10px;
grid-column: 1;
grid-row: 1;
pointer-events: none;
}
我认为在此处包括按钮样式并不重要。稍后我有一个Codepen来展示幻灯片演示。
.bullets
容器位于幻灯片网格的第2行中。它与中心的子弹对齐。
.bullets {
grid-column: 1;
grid-row: 2;
display: flex;
justify-content: center;
align-items: center;
gap: 12px;
}
每个子弹都是圆形的,与活动幻灯片相对应的一个。
到目前为止,我们有这个。它尚不正常,但风格。
JavaScript
首先,让我们在变量中保留一些有用的东西。
// We keep references to all needed elements.
const slideshow = document.querySelector(".slideshow");
const slidesContainer = slideshow.querySelector(".slides");
const slides = slideshow.querySelectorAll(".slide");
const bulletContainer = slideshow.querySelector(".bullets");
const prev = slideshow.querySelector(".prev");
const next = slideshow.querySelector(".next");
const length = slides.length;
然后,让我们根据幻灯片计数创建子弹。我在这里使用文档fragmet将新元素插入DOM。
// Automatically create the bullets based on
// the amount of slides we have.
const fragment = document.createDocumentFragment();
slides.forEach((slide, index) => {
const bullet = document.createElement("span");
bullet.classList.add("bullet");
if (!index) {
// Set the first bullet as active
bullet.classList.add("active");
}
// Add the click functionality to the bullet
bullet.addEventListener("click", () => active.slide = index);
fragment.appendChild(bullet);
});
bulletContainer.appendChild(fragment);
// Keep a reference to the bullets
// so we can add/remove the .active class.
const bullets = bulletContainer.querySelectorAll(".bullet");
现在,让我们在按钮中添加功能。单击.next
按钮时,我们要将active
索引增加1,然后单击.prev
按钮时,我们希望将active
索引减少1.
当我们到达最后一个幻灯片并单击下一步时,它应该返回第一个。当我们处于首先并单击之前,它应该转到最后一个。但是,我真的不喜欢将此逻辑放入点击处理程序中,因为通过单击.next
,我不在乎哪个是下一个。请告诉我下一个。因此,我使用了JavaScript代理的魔力。您可能已经注意到,在子弹单击侦听器中,我使用active.slide
设置了活动幻灯片索引。这就是它的发展。
// We use Proxy to include some kind of reactivity.
// By just changing active.slide, the DOM gets
// changed automatically, without the need to
// make the DOM changes within the event handlers.
const active = new Proxy(
{ slide: 0 },
{
set(obj, prop, value) {
if (prop == "slide") {
// If we click "prev" while on the first slide
// then go to the last one.
if (value < 0) {
value = length - 1;
}
// If we click "next" while on the last slide
// then go to the first one.
if (value >= length) {
value = 0;
}
// Make the appropriate DOM changes
slidesContainer.style.setProperty("--slide", value);
bullets[obj[prop]].classList.remove("active");
bullets[value].classList.add("active");
}
// Actually set the active.slide prop to it's new value
obj[prop] = value;
return true;
}
}
);
如MDN网站
所述代理对象允许您创建一个可以代替原始对象使用的对象,但可以重新定义基本对象操作,例如get,设置和定义属性。
我将代理作为目标对象作为第一个参数,而处理程序则是第二个参数。在处理程序中有一个设置器函数。
第一个参数obj
是目标对象{ slide: 0 }
。第二个参数prop
是我们正在设置的属性,在我们的情况下,slide
。第三个参数是我们要设置为属性的值。
进入六个,我将所有用于计算哪个幻灯片是应显示的幻灯片的逻辑,并进行适当的DOM更改。我们可以看到,必须完成的唯一DOM更改是设置--active
CSS变量适当的活动幻灯片索引,并在相应的子弹上切换.active
类。
在JavaScript代理的帮助下,我能够分开关注点,并在.prev
和.next
按钮上添加非常简单的单击处理程序。
// Add the prev/next functionality to the buttons
prev.addEventListener("click", () => active.slide--);
next.addEventListener("click", () => active.slide++);
这是完整的编码器。
最后,这是另一个编码。它与最后一个相同,但是如果按空格,它将显示为3D以显示元素如何彼此放置,以及“窗口”如何工作。
我希望您喜欢我用来制作此幻灯片的技术。我很想听听更好的选择。我一直在学习新的东西。
感谢您阅读此远!