<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>轮播卡片</title>
<style>
body {
background: #0e0e10;
color: var(--primary-text);
}
section {
max-width: 1400px;
margin: 0 auto;
padding: 6rem 3rem;
}
.section-header {
text-align: center;
margin-bottom: 4rem;
}
.section-tag {
font-size: 0.875rem;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--accent-cyan);
font-weight: 600;
margin-bottom: 1rem;
}
.section-title {
font-size: 2.8rem;
font-weight: 700;
letter-spacing: 0.03em;
margin-bottom: 1rem;
color: #e0bb7d;
}
.section-subtitle {
font-size: 1rem;
color: var(--text-gray);
letter-spacing: 0.03em;
max-width: 600px;
margin: 0 auto;
}
.categories-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 1.5rem;
margin-bottom: 5rem;
}
.category-card {
padding: 2rem 1rem;
text-align: center;
border: 1px solid var(--border-gray);
cursor: pointer;
transition: var(--transition);
opacity: 0;
transform: translateY(2rem);
}
.category-card.visible {
opacity: 1;
transform: translateY(0);
}
.category-card:hover {
border-color: var(--accent-cyan);
box-shadow: 0 0 20px rgba(0, 242, 255, 0.1);
}
.category-card i {
font-size: 2.5rem;
color: var(--primary-text);
margin-bottom: 1rem;
display: block;
transition: var(--transition);
}
.category-card:hover i {
color: var(--accent-cyan);
transform: scale(1.1);
}
.category-card p {
font-size: 1rem;
font-weight: 600;
letter-spacing: 0.03em;
}
#platform {
overflow: hidden;
padding: 6rem 0; /* 左右铺满,上下留白 */
}
.carousel-viewport {
width: 100%;
overflow: hidden;
cursor: grab;
position: relative;
user-select: none; /* 防止拖拽时选中文字 */
}
.carousel-viewport:active {
cursor: grabbing;
}
.carousel-track {
display: flex;
padding: 20px 0;
}
.category-card-slide {
/* 核心:4个卡片一行,减去间隙 */
flex: 0 0 25%;
padding: 0 15px;
box-sizing: border-box;
}
.card-content {
aspect-ratio: 3 / 4; /* 严格执行4:3 */
background: var(--card-bg);
border: 1px solid var(--border-gray);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
transition: var(--transition);
border-radius: 15px; /* 轻微圆角增加高级感 */
}
.card-content i {
font-size: 3.2rem;
color: var(--primary-text);
margin-bottom: 1.2rem;
transition: var(--transition);
}
.card-content p {
font-size: 1.1rem;
font-weight: 500;
color: var(--primary-text);
}
/* 悬浮交互 */
.card-content:hover {
border-color: #e0bb7d;
background: rgba(224, 187, 125, 0.05);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
transform: translateY(-5px);
}
.card-content:hover i {
color: #e0bb7d;
transform: scale(1.1);
}
/* 指示点 */
.carousel-dots {
display: flex;
justify-content: center;
gap: 12px;
margin-top: 2.5rem;
}
.carousel-viewport {
user-select: none;
-webkit-user-drag: none;
}
.dot {
width: 6px;
height: 6px;
background: #333;
border-radius: 50%;
cursor: pointer;
transition: all 0.4s ease;
}
.dot.active {
width: 20px;
border-radius: 10px;
background: #e0bb7d;
}
/* 移动端适配 */
@media (max-width: 1024px) {
.category-card-slide {
flex: 0 0 33.333%;
}
}
@media (max-width: 768px) {
.category-card-slide {
flex: 0 0 50%;
}
.card-content i {
font-size: 2.5rem;
}
}
</style>
</head>
<body>
<section id="platform">
<div class="section-header">
<div class="section-tag">Platform</div>
<h2 class="section-title">筑能平台</h2>
<p class="section-subtitle">
按建筑类型快速进入场景 (支持鼠标左右滑动)
</p>
</div>
<div class="carousel-viewport" id="carouselViewport">
<div class="carousel-track" id="categoriesTrack">
<!-- 由 JavaScript 填充 -->
</div>
</div>
<div class="carousel-dots" id="carouselDots"></div>
</section>
<script>
const categories = [
{
title: "写字楼",
icon: "fas fa-building",
url: "./list.html?type=workplace",
backgroundImage: "./images/cases/szsws.png",
},
{
title: "工厂",
icon: "fas fa-industry",
url: "./list.html?type=factory",
backgroundImage: "./images/banners/scene/factory.png",
},
{
title: "学校",
icon: "fas fa-school",
url: "./list.html?type=school",
backgroundImage: "./images/cases/syxx.png",
},
{
title: "医院",
icon: "fas fa-hospital",
url: "./list.html?type=hospital",
backgroundImage: "./images/banners/scene/hospital.png",
},
{
title: "其他",
icon: "fas fa-ellipsis-h",
url: "./list.html?type=other",
backgroundImage: "./images/banners/scene/other.png",
}, // 也可以跳回内部详情页
];
// ============================================================
// 2. 筑能平台:彻底解决回弹的轮播逻辑
// ============================================================
/**
* 2.1 获取轮播相关DOM元素
*/
const viewport = document.getElementById("carouselViewport");
const track = document.getElementById("categoriesTrack");
const dotsContainer = document.getElementById("carouselDots");
/**
* 2.2 轮播配置变量
*/
let items = categories;
const itemsToShow = 4; // 页面可见数量
let currentIndex = itemsToShow;
let isDragging = false;
let isMoving = false;
let startX = 0;
let dragStartX = 0;
let timer = null;
/**
* 2.3 初始化轮播
* - 克隆首尾节点实现无缝循环
* - 创建指示点
* - 设置初始位置
* - 启动自动轮播定时器
*/
function initCarousel() {
// 克隆节点
const headClones = items.slice(0, itemsToShow);
const tailClones = items.slice(-itemsToShow);
const combinedItems = [...tailClones, ...items, ...headClones];
// 清空并重新填充轨道
track.innerHTML = "";
combinedItems.forEach((c) => {
const card = document.createElement("div");
card.className = "category-card-slide";
card.innerHTML = `
<div class="card-content" style="background-image: url(${c.backgroundImage}); background-size: cover; background-position: center;">
<i class="${c.icon}" style="display: none;"></i>
<p style="font-size: 30px; font-weight: 600; margin-top: 330px; margin-bottom: 0; color: #fff; text-shadow: 0 0 10px rgba(0, 0, 0, 0.5);">${c.title}</p>
</div>
`;
card.onclick = (e) => {
if (isMoving) {
e.preventDefault();
e.stopPropagation();
return;
}
location.href = c.url;
};
track.appendChild(card);
});
// 创建指示点
dotsContainer.innerHTML = "";
items.forEach((_, i) => {
const dot = document.createElement("div");
dot.className = `dot ${i === 0 ? "active" : ""}`;
dot.onclick = () => {
currentIndex = i + itemsToShow;
updatePosition(true);
};
dotsContainer.appendChild(dot);
});
// 设置初始位置
updatePosition(false);
// 启动自动轮播
startTimer();
}
/**
* 2.4 更新轮播位置
* @param {boolean} animation - 是否启用过渡动画
*/
function updatePosition(animation = true) {
const cardWidth = viewport.offsetWidth / itemsToShow; // 计算单张卡片宽度
track.style.transition = animation
? "transform 0.5s cubic-bezier(0.2, 1, 0.3, 1)"
: "none";
track.style.transform = `translateX(${-currentIndex * cardWidth}px)`;
// 更新指示点状态
updateDots();
}
/**
* 2.5 更新指示点激活状态
*/
function updateDots() {
const dots = document.querySelectorAll(".dot");
if (dots.length === 0) return;
const logicIndex =
(currentIndex - itemsToShow + items.length) % items.length;
dots.forEach((dot, i) => {
dot.classList.toggle("active", i === logicIndex);
});
}
/**
* 2.6 获取事件中的X坐标(支持鼠标和触摸事件)
* @param {Event} e - 事件对象
* @returns {number} - X坐标
*/
function getPosX(e) {
return e.type.includes("mouse") ? e.pageX : e.touches[0].clientX;
}
/**
* 2.7 拖拽开始事件处理
* @param {Event} e - 事件对象
*/
function onDragStart(e) {
isDragging = true;
isMoving = false;
startX = getPosX(e);
// 清除自动轮播定时器
clearInterval(timer);
track.style.transition = "none";
// 获取当前轨道的平移量
const style = window.getComputedStyle(track);
const matrix = new WebKitCSSMatrix(style.transform);
dragStartX = matrix.m41;
}
/**
* 2.8 拖拽移动事件处理
* @param {Event} e - 事件对象
*/
function onDragMove(e) {
if (!isDragging) return;
const currentX = getPosX(e);
const diff = currentX - startX;
// 灵敏度判断:移动超过5像素视为有效移动
if (Math.abs(diff) > 5) {
isMoving = true;
}
// 实时更新轨道位置
track.style.transform = `translateX(${dragStartX + diff}px)`;
}
/**
* 2.9 拖拽结束事件处理
* @param {Event} e - 事件对象
*/
function onDragEnd(e) {
if (!isDragging) return;
isDragging = false;
const endX = e.type.includes("touch")
? e.changedTouches[0].clientX
: e.pageX;
const diff = endX - startX;
// 彻底防回弹逻辑
const threshold = 40; // 切换阈值:40像素
if (diff < -threshold) {
currentIndex++; // 向左划,显示下一张
} else if (diff > threshold) {
currentIndex--; // 向右划,显示上一张
}
// 强制对齐到正确位置
updatePosition(true);
// 延迟重置移动状态
setTimeout(() => {
isMoving = false;
}, 50);
// 重新启动自动轮播
startTimer();
}
/**
* 2.10 无缝循环检测(过渡动画结束后)
*/
track.addEventListener("transitionend", () => {
// 向右滚动到头时,跳转到开头克隆节点
if (currentIndex >= items.length + itemsToShow) {
currentIndex = itemsToShow;
updatePosition(false);
}
// 向左滚动到头时,跳转到末尾克隆节点
else if (currentIndex < itemsToShow) {
currentIndex = items.length + itemsToShow - 1;
updatePosition(false);
}
});
/**
* 2.11 启动自动轮播定时器
*/
function startTimer() {
clearInterval(timer);
timer = setInterval(() => {
currentIndex++;
updatePosition(true);
}, 3000);
}
/**
* 2.12 绑定轮播事件监听器
*/
// 拖拽开始
viewport.addEventListener("mousedown", onDragStart);
viewport.addEventListener("touchstart", onDragStart, { passive: true });
// 拖拽移动
window.addEventListener("mousemove", onDragMove);
window.addEventListener("touchmove", onDragMove, { passive: false });
// 拖拽结束
window.addEventListener("mouseup", onDragEnd);
window.addEventListener("touchend", onDragEnd);
/**
* 2.13 初始化轮播并监听窗口大小变化
*/
initCarousel();
window.addEventListener("resize", () => {
updatePosition(false);
});
</script>
</body>
</html>