我对这个游戏有点陌生。我真的很困惑,如果您有 5 分钟的时间,我希望得到一些指导。 我认为屏幕截图足够清晰,可以看到我在说什么,但我想通过在移动设备上滑动和捕捉三层文本来进行某种导航(从 md 屏幕及其他位置,它是常规的“顶部”彼此的”布局)水平。 我使用 laravel 10 +filamentphp v3 + tailwindCSS(使用文档中的默认配置主题)。
这是默认状态下没有交互的视图,其中 #surface 处于活动状态;另外两个 #shallow 和 #deep 处于非活动状态。
<div class="flex justify-center items-center mb-8 min-h-96">
<h1 class="text-4xl font-sans font-bold text-amber-500 w-full m-8">{{ $fiche->titre }}</h1>
</div>
<div id="section" class="flex flex-row md:flex-col h-full truncate mt-auto max-md:flex-grow ">
@if ($fiche->text_surface)
<div id="surface" class="md:w-full text-wrap w-full min-w-2 w-0.5">
<div class="p-4 bg-blue-100 w-full h-full md:columns-2 pb-8">
<p class="mx-8 max-md:hidden">{{ $fiche->text_surface }}</p>
</div>
</div>
@endif
<div id="shallow" class=" md:w-full text-wrap">
@if ($fiche->text_shallow)
<div class="p-4 bg-blue-200 w-full h-full md:columns-2 pb-8">
<p class=" mx-8 mb-8 ">{{ $fiche->text_shallow }}</p>
</div>
@else
<div class="p-4 bg-blue-200 w-full h-full"></div>
@endif
</div>
<div id="deep" class="min-w-2 w-0.5 md:w-full h-auto text-wrap">
@if ($fiche->text_deep)
<div class="p-4 bg-blue-300 w-full h-full md:columns-2 pb-8">
<p class="max-md:hidden mx-8 mb-8">{{ $fiche->text_deep }}</p>
</div>
@else
<div class="p-4 bg-blue-300 w-full h-full"></div>
@endif
</div>
</div>
<div class=" p-1 bg-blue-500 "></div>
<div class="p-0.5 bg-amber-400 "></div>
您知道如何做到这一点吗?是否有(轻量级)JS 库或某种复制/粘贴元素可以做到这一点? 如果没有,你至少能告诉我这种“导航”是怎么称呼的吗?这样可以方便我进一步的研究?
我尝试了原生顺风的滚动和捕捉功能,它工作得很好,但它消除了侧面的栏(这对我的设计很重要),并且底部有一个我不想要的小水平滚动条... 之后,我尝试了一些 javacript 并操纵 DOM 在需要的地方切换类,但它以某种方式破坏了很多东西,比如文本没有重新出现或突然有 3 公里的垂直和水平滚动......
非常感谢。
你至少能告诉我这种“导航”是怎么称呼的吗?这样可以方便我进一步的研究?
我相信您正在寻找的东西类似于滑块或旋转木马。在线搜索“javascript滑块轮播”。
以下是 JavaScript 轮播库的链接:Splide 和 glide.js。
构建您自己的滑块/轮播教程:如何构建简单的轮播。
但是,这些都不能准确提供您正在寻找的内容。您附加的图像显示类似于“手风琴”的内容:轮播中所有上一个和下一个项目的条子/预览。
您可以使用以下代码片段作为起点编写自己的代码。这是我从过去参与的多个项目中提取的普通 JavaScript。它模拟您问题中图像的 UI。
我没有包含允许左右滑动的触摸手势代码。这可以通过库添加,例如在此
link处的
hammer.js
或使用 HTML DOM 中的本机 touch
事件。使用两个控件来测试左右滑动方向。
下面的代码片段通过使用
spacer
变量来工作,该变量分配给 CSS 变量的值(定义为 --spacer
)。 spacer
中的 pixels
变量用于设置每个 .panel
项目的宽度以及 left
项目的 .panel
位置,以在可见 的每一侧创建“堆栈” .panel
元素。
我在 JavaScript 顶部添加了一个标志
useSpacer
。如果设置为 true
,则会呈现“堆叠”间距效果。如果 useSpacer
设置为 false
,则呈现通用滑块/轮播 UI,而不会预览上一个或下一个元素。
将
.panel-container
设置为 display: grid;
,并且通过设置 CSS 属性:.panel
,将每个子 grid-area: 1/1
元素堆叠在彼此之上。
CSS
transition
用于 transform
上,以动画方式更改活动的 .panel
元素。
document.addEventListener("DOMContentLoaded", initialize);
let panelContainerEl,
panelElList;
// Units in 'px'
let panelContainerWidth,
panelWidth;
let numOfPanels,
spacer,
useSpacer = true;
function initialize() {
panelContainerEl = document.querySelector(".panel-container");
panelContainerWidth = panelContainerEl.getBoundingClientRect().width;
panelElList = panelContainerEl.querySelectorAll(".panel");
numOfPanels = panelElList.length;
if (useSpacer) {
// Get CSS variable
// developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration/getPropertyValue
// 'getPropertyValue()' return value: "If not set, returns the empty string."
spacer = getComputedStyle(panelContainerEl).getPropertyValue('--spacer');
spacer = spacer === '' || Number.isNaN(spacer) ? 0 : parseInt(spacer, 10);
panelWidth = panelContainerWidth - ((numOfPanels - 1) * spacer);
const panelWidthWithUnits = `${panelWidth}px`;
panelElList.forEach(panelEl => panelEl.style.width = panelWidthWithUnits);
// Initialize panel display. Set to the first panel
// (at index '0') in 'panelElList'.
selectPanel(panelElList[0], 0, false);
} else {
panelElList[0].classList.add("panel-in-view");
}
document.querySelectorAll(".controls .ctrl")
.forEach(el => el.addEventListener("click", controlClickHandler));
//panelElList.forEach(panelEl => swipeDetect(panelEl, swipeHandler));
}
/* --------------- Display panel -----------------------------------
*/
function displayPanel(dirIndex) {
const currentPanelEl = panelContainerEl.querySelector(".panel-in-view");
// Find the index of the panel element within the panel element list
const currentIndex = [].slice.call(panelElList).indexOf(currentPanelEl);
const targetIndex = currentIndex + dirIndex;
if (targetIndex < 0) {
document.querySelector("#ctrl-message").textContent = "Currently at START of panels";
return;
} else if (targetIndex > numOfPanels - 1) {
document.querySelector("#ctrl-message").textContent = "Currently at END of panels";
return;
} else {
document.querySelector("#ctrl-message").textContent = "";
}
const animate = true;
selectPanel(currentPanelEl, targetIndex, animate);
}
function selectPanel(currentPanelEl, targetIndex, animate) {
if (!currentPanelEl) {
return;
}
const currentIndex = [].slice.call(panelElList).indexOf(currentPanelEl);
const targetPanelEl = panelElList[targetIndex];
repositionPanels(currentIndex, targetIndex);
if (useSpacer) {
setSpacers(targetIndex);
}
if (animate === false) {
repositionPanels(currentIndex, targetIndex, panelElList);
currentPanelEl.classList.remove("panel-in-view");
targetPanelEl.classList.add("panel-in-view");
return;
}
// -------- Set up and trigger transition for the CURRENT panel
const transitionEndHandlerCur = function() {
this.removeEventListener('transitionend', transitionEndHandlerCur);
this.classList.remove('is-moving');
};
currentPanelEl.addEventListener('transitionend', transitionEndHandlerCur);
// Trigger the transform transition
currentPanelEl.classList.add('is-moving');
currentPanelEl.classList.remove('panel-in-view');
// -------- Set up and trigger transition for the TARGET panel
const transitionEndHandlerTar = function() {
this.classList.remove('is-moving');
this.removeEventListener("transitionend", transitionEndHandlerTar);
};
targetPanelEl.addEventListener("transitionend", transitionEndHandlerTar);
// Add the "selected" class to the selected index element
targetPanelEl.classList.add('is-moving', 'panel-in-view');
}
function repositionPanels(currentIndex, targetIndex) {
// Reposition list items that have a lower index position to the "left"
if (currentIndex < targetIndex) {
// Add left position class
for (let n = 0; n < targetIndex; n++) {
panelElList[n].classList.add('translated-left');
}
} else {
// Remove left position class
for (let n = targetIndex; n < panelElList.length; n++) {
panelElList[n].classList.remove('translated-left');
}
}
}
function setSpacers(targetIndex) {
panelElList.forEach((panelEl, index) => {
if (index < targetIndex) {
panelEl.style.transform = `translateX(calc(-${panelWidth}px + ${(spacer * (index + 1))}px))`;
panelEl.style.zIndex = numOfPanels - index;
} else if (index > targetIndex) {
panelEl.style.transform = `translateX(calc(${panelWidth}px - ${(spacer * (index - 1) * -1)}px))`;
panelEl.style.zIndex = numOfPanels + index;
} else if (index === targetIndex) {
panelEl.style.transform = `translateX(${(spacer * index)}px)`;
panelEl.style.zIndex = numOfPanels;
}
});
}
/* --------------- Scroll controls -----------------------------------
*/
function controlClickHandler(e) {
e.preventDefault();
const dirIndex = this.getAttribute("data-dir") === "get-from-right" ? 1 : -1;
displayPanel(dirIndex);
}
.panel-container {
--panel-lr-padding: 1rem;
--spacer: 8;
/* Units in 'px' */
display: grid;
height: fit-content;
/* Explicitly add the 'position: relative;' so that '.panel'
child elements are positioned by its 'left' CSS property. */
position: relative;
/* Hide container panels that are translated to the right or left. */
overflow: hidden;
height: 100%;
/* This is required to enable correct setting of panel width */
box-sizing: border-box;
}
.panel {
grid-area: 1/1;
position: relative;
top: 0;
/* By default each panel is positioned off-screen */
left: 0;
transform: translateX(100%);
padding: var(--panel-lr-padding);
/* This is required to enable correct setting of panel width */
box-sizing: border-box;
}
.panel-in-view {
transform: translateX(0);
}
/* Panel hidden from view to the left */
.translated-left {
transform: translateX(-100%);
}
/* Panel hidden from view to the right */
.translated-right {
transform: translateX(100%);
}
/* The 'is-moving' class is assigned to the DOM element
that is moving out of the viewport. */
.is-moving {
transition: transform 0.4s;
}
.controls {
display: grid;
grid-auto-flow: column;
justify-content: start;
align-items: center;
gap: .5rem;
margin: 1rem 0;
}
.controls .ctrl {
padding: .5rem;
cursor: pointer;
border: 1px solid grey;
user-select: none;
}
#ctrl-message {
display: block;
color: red;
}
#shallow {
background-color: rgb(205, 233, 255);
}
#mid {
background-color: rgb(143, 201, 247);
}
#deep {
background-color: rgb(81, 139, 184);
color: rgba(250, 250, 250);
}
#extra-deep {
background-color: rgb(51, 95, 129);
color: rgba(250, 250, 250);
}
<h1>Header 1</h1>
<div class="main-text">
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has
survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.
</div>
<div class="controls">
<a class="ctrl" data-dir="get-from-right">Swipe Left</a>
<a class="ctrl" data-dir="get-from-left">Swipe Right</a>
<span id="ctrl-message"></span>
</div>
<div class="panel-container">
<div id="shallow" class="panel">
<p>The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions
from the 1914 translation by H. Rackham.</p>
</div>
<div id="mid" class="panel">
<p>It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content
here, content here', making it look like readable English.</p>
<p>Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked
up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus
Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line
in section 1.10.32.</p>
<p>There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum,
you need to be sure there isn't anything embarrassing hidden in the middle of text.</p>
</div>
<div id="deep" class="panel">
<p>There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum,
you need to be sure there isn't anything embarrassing hidden in the middle of text.</p>
<p>The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions
from the 1914 translation by H. Rackham.</p>
</div>
<div id="extra-deep" class="panel">
<p>There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum,
you need to be sure there isn't anything embarrassing hidden in the middle of text.</p>
</div>
</div>