Skip to content

Commit b940bdc

Browse files
committed
minor
1 parent e8c4034 commit b940bdc

File tree

2 files changed

+151
-195
lines changed

2 files changed

+151
-195
lines changed

assets/css/style.css

Lines changed: 19 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -851,72 +851,6 @@ h1, h2, h3, p, li, .caption, .small {
851851
}
852852

853853

854-
/* ===== Mobile carousel centering fix ===== */
855-
@media (max-width: 768px) {
856-
.carousel-container{
857-
max-width: 100%;
858-
margin: 16px auto;
859-
padding: 8px 0 44px; /* 底部给 caption 留空间 */
860-
overflow: visible;
861-
}
862-
863-
.carousel-window{
864-
width: 100%;
865-
overflow: hidden;
866-
-webkit-mask-image: none !important;
867-
mask-image: none !important;
868-
}
869-
870-
.carousel-track{
871-
display: flex;
872-
align-items: center;
873-
justify-content: center; /* 关键:轨道内容居中 */
874-
gap: 0; /* 关键:去掉间隔避免偏移 */
875-
padding: 0;
876-
transform: none !important; /* 若 JS/旧样式有 transform,先压住 */
877-
}
878-
879-
.carousel-card{
880-
flex: 0 0 100%;
881-
max-width: 100%;
882-
width: 100%;
883-
margin: 0 auto;
884-
opacity: 0;
885-
transform: none !important;
886-
filter: none !important;
887-
transition: opacity .25s ease;
888-
}
889-
890-
.carousel-card.is-active{
891-
opacity: 1;
892-
z-index: 2;
893-
}
894-
895-
.video-wrapper{
896-
width: 100%;
897-
margin: 0 auto;
898-
border-radius: 12px;
899-
}
900-
901-
/* 只显示当前卡片标题,且居中 */
902-
.carousel-card .caption{
903-
text-align: center;
904-
margin-top: 10px;
905-
font-size: 15px;
906-
line-height: 1.35;
907-
}
908-
909-
/* 按钮垂直居中到视频中线,并贴边 */
910-
.nav-btn{
911-
top: calc(50% - 22px); /* 对齐视频视觉中心 */
912-
width: 42px;
913-
height: 42px;
914-
z-index: 5;
915-
}
916-
.prev-btn{ left: 8px; }
917-
.next-btn{ right: 8px; }
918-
}
919-
920854
/* 基础设置 */
921855
.carousel-container {
922856
position: relative;
@@ -1041,4 +975,22 @@ h1, h2, h3, p, li, .caption, .small {
1041975
@media (max-width: 768px) {
1042976
.prev-btn { left: 5px; }
1043977
.next-btn { right: 5px; }
1044-
}
978+
}
979+
980+
/* 添加这段精简的移动端适配 */
981+
@media (max-width: 768px) {
982+
.carousel-container {
983+
padding-bottom: 40px; /* 给下方的按钮留点空间 */
984+
}
985+
986+
/* 让按钮在手机上稍微大一点,好点按 */
987+
.nav-btn {
988+
width: 44px;
989+
height: 44px;
990+
}
991+
992+
/* 确保视频圆角和阴影正常 */
993+
.video-wrapper {
994+
box-shadow: 0 4px 15px rgba(0,0,0,0.4);
995+
}
996+
}

assets/js/main.js

Lines changed: 132 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -742,143 +742,147 @@ function initDemoRotator(){
742742
}
743743

744744
window.addEventListener('load', ()=>{ try{ initDemoRotator(); }catch(e){} });
745-
// 1. 初始化变量
746-
// 1. 获取元素
747-
const track = document.getElementById('track');
748-
const originalCards = document.querySelectorAll('.carousel-card');
749-
const prevBtn = document.getElementById('prevBtn');
750-
const nextBtn = document.getElementById('nextBtn');
751-
const gapPx = 15; // 必须和 CSS 里的 gap 保持一致
752-
753-
// 2. 动态获取当前是从 60% 还是 75% 渲染
754-
function getCardWidthPercent() {
755-
return window.innerWidth < 768 ? 75 : 60;
756-
}
757-
758-
// 3. 克隆首尾 (Clone)
759-
const firstClone = originalCards[0].cloneNode(true);
760-
const lastClone = originalCards[originalCards.length - 1].cloneNode(true);
761-
track.appendChild(firstClone);
762-
track.insertBefore(lastClone, originalCards[0]);
763-
764-
let allCards = document.querySelectorAll('.carousel-card');
765-
let currentIndex = 1;
766-
let isTransitioning = false;
767-
768-
// 4. 核心:更新位置 (使用纯 CSS calc,稳如老狗)
769-
function updateTrack(animate) {
770-
if (animate) {
771-
track.style.transition = 'transform 0.4s cubic-bezier(0.25, 1, 0.5, 1)';
772-
} else {
773-
track.style.transition = 'none';
745+
/* =========================================
746+
无缝轮播 (修复回滚时的卡片缩放闪烁问题)
747+
========================================= */
748+
function initCarousel() {
749+
const track = document.getElementById('track');
750+
if (!track) return;
751+
752+
const originalCards = document.querySelectorAll('.carousel-card');
753+
const prevBtn = document.getElementById('prevBtn');
754+
const nextBtn = document.getElementById('nextBtn');
755+
const gapPx = 15;
756+
757+
// 1. 克隆首尾
758+
const firstClone = originalCards[0].cloneNode(true);
759+
const lastClone = originalCards[originalCards.length - 1].cloneNode(true);
760+
track.appendChild(firstClone);
761+
track.insertBefore(lastClone, originalCards[0]);
762+
763+
let allCards = document.querySelectorAll('.carousel-card');
764+
let currentIndex = 1;
765+
let isTransitioning = false;
766+
767+
// 2. 动态获取宽度
768+
function getCardWidthPercent() {
769+
return window.innerWidth < 768 ? 75 : 60;
774770
}
775771

776-
const wPercent = getCardWidthPercent();
777-
778-
// 公式解释:
779-
// 居中偏移量 = (100% - 卡片宽度%) / 2
780-
// 移动距离 = 居中偏移量 - (当前索引 * (卡片宽度% + 间距px))
781-
const centerOffset = (100 - wPercent) / 2;
782-
const val = `calc(${centerOffset}% - ${currentIndex} * (${wPercent}% + ${gapPx}px))`;
783-
784-
track.style.transform = `translateX(${val})`;
785-
}
786-
787-
// 5. 切换逻辑
788-
function switchSlide(direction) {
789-
if (isTransitioning) return;
790-
791-
// 防止快速点击越界
792-
if (direction === 1 && currentIndex >= allCards.length - 1) return;
793-
if (direction === -1 && currentIndex <= 0) return;
794-
795-
currentIndex += direction;
796-
isTransitioning = true;
797-
updateTrack(true);
798-
updateActive();
799-
}
800-
801-
// 6. 事件绑定 (暴力绑定 click 和 touchend)
772+
// 3. 更新轨道位置
773+
function updateTrack(animate) {
774+
if (animate) {
775+
track.style.transition = 'transform 0.4s cubic-bezier(0.25, 1, 0.5, 1)';
776+
} else {
777+
track.style.transition = 'none';
778+
}
779+
const wPercent = getCardWidthPercent();
780+
const centerOffset = (100 - wPercent) / 2;
781+
const val = `calc(${centerOffset}% - ${currentIndex} * (${wPercent}% + ${gapPx}px))`;
782+
track.style.transform = `translateX(${val})`;
783+
}
802784

803-
// 电脑端点击
804-
nextBtn.addEventListener('click', (e) => { e.preventDefault(); switchSlide(1); });
805-
prevBtn.addEventListener('click', (e) => { e.preventDefault(); switchSlide(-1); });
785+
// 4. 更新激活状态 (控制缩放和透明度)
786+
function updateActive() {
787+
allCards.forEach((card, index) => {
788+
const video = card.querySelector('video');
789+
790+
if (index === currentIndex) {
791+
// 当前激活卡片
792+
card.classList.add('is-active');
793+
// 直接操作样式,保证优先级
794+
card.style.opacity = '1';
795+
card.style.transform = 'scale(1)';
796+
card.style.filter = 'blur(0)';
797+
card.style.zIndex = '10';
798+
if (video) video.play().catch(()=>{});
799+
} else {
800+
// 非激活卡片
801+
card.classList.remove('is-active');
802+
card.style.opacity = '0.4';
803+
card.style.transform = 'scale(0.9)';
804+
card.style.filter = 'blur(1px)';
805+
card.style.zIndex = '1';
806+
if (video) video.pause();
807+
}
808+
});
809+
}
806810

807-
// 手机端触摸 (加锁防止双重触发)
808-
let touchLock = false;
809-
function handleTouch(e, dir) {
810-
if(touchLock) return;
811-
e.preventDefault(); e.stopPropagation(); // 阻止冒泡
812-
switchSlide(dir);
813-
touchLock = true;
814-
setTimeout(() => touchLock = false, 300);
815-
}
816-
nextBtn.addEventListener('touchend', (e) => handleTouch(e, 1));
817-
prevBtn.addEventListener('touchend', (e) => handleTouch(e, -1));
818-
819-
// 7. 手机滑动支持 (Swipe)
820-
let touchStartX = 0;
821-
track.addEventListener('touchstart', e => { touchStartX = e.changedTouches[0].screenX; }, {passive: true});
822-
track.addEventListener('touchend', e => {
823-
const touchEndX = e.changedTouches[0].screenX;
824-
if (touchEndX < touchStartX - 50) switchSlide(1); // 左滑 -> 下一张
825-
if (touchEndX > touchStartX + 50) switchSlide(-1); // 右滑 -> 上一张
826-
}, {passive: true});
827-
828-
// 8. 无缝循环 & 视频同步
829-
track.addEventListener('transitionend', () => {
830-
isTransitioning = false;
831-
let targetIndex = -1;
832-
833-
// 到了克隆的最后一张 -> 跳回真实第1张
834-
if (currentIndex === allCards.length - 1) targetIndex = 1;
835-
// 到了克隆的第一张 -> 跳回真实最后一张
836-
else if (currentIndex === 0) targetIndex = allCards.length - 2;
837-
838-
if (targetIndex !== -1) {
839-
// 视频同步
840-
const v1 = allCards[currentIndex].querySelector('video');
841-
const v2 = allCards[targetIndex].querySelector('video');
842-
if (v1 && v2) v2.currentTime = v1.currentTime;
843-
844-
// 瞬移
845-
track.classList.add('no-transition');
846-
allCards.forEach(c => c.classList.add('no-transition'));
811+
// 5. 切换逻辑
812+
function switchSlide(direction) {
813+
if (isTransitioning) return;
814+
currentIndex += direction;
815+
isTransitioning = true;
847816

848-
currentIndex = targetIndex;
849-
updateTrack(false); // 重新定位
850-
updateActive();
851-
852-
void track.offsetHeight; // 强制重绘
853-
requestAnimationFrame(() => {
854-
track.classList.remove('no-transition');
855-
allCards.forEach(c => c.classList.remove('no-transition'));
856-
});
817+
updateActive(); // 先触发缩放动画
818+
updateTrack(true); // 再触发位移动画
857819
}
858-
});
859820

860-
// 9. 激活状态 (只播中间,两边暂停)
861-
function updateActive() {
862-
allCards.forEach((card, index) => {
863-
const v = card.querySelector('video');
864-
if (index === currentIndex) {
865-
card.classList.add('is-active');
866-
if (v) {
867-
v.muted = true;
868-
v.playsInline = true;
869-
// 必须加 catch 防止报错
870-
v.play().catch(()=>{});
821+
// 6. 监听动画结束 (处理瞬间回滚 + 防止缩放闪烁)
822+
track.addEventListener('transitionend', () => {
823+
if (!isTransitioning) return;
824+
isTransitioning = false;
825+
826+
let targetIndex = -1;
827+
// 检测是否到了边界
828+
if (currentIndex === allCards.length - 1) targetIndex = 1;
829+
else if (currentIndex === 0) targetIndex = allCards.length - 2;
830+
831+
if (targetIndex !== -1) {
832+
// === 核心修复开始 ===
833+
834+
// 1. 给所有卡片加上禁止动画类
835+
// 这步至关重要:防止从 scale(0.9) 变成 scale(1) 时播放动画
836+
allCards.forEach(c => c.classList.add('no-transition'));
837+
track.style.transition = 'none';
838+
839+
// 2. 视频同步 (防止黑屏)
840+
const currentVideo = allCards[currentIndex].querySelector('video');
841+
const targetVideo = allCards[targetIndex].querySelector('video');
842+
if (currentVideo && targetVideo) {
843+
targetVideo.currentTime = currentVideo.currentTime;
844+
targetVideo.play().catch(()=>{});
871845
}
872-
} else {
873-
card.classList.remove('is-active');
874-
if (v) v.pause();
846+
847+
// 3. 瞬间瞬移位置
848+
currentIndex = targetIndex;
849+
updateTrack(false);
850+
851+
// 4. 瞬间强制应用激活样式 (因为加了 no-transition,这里会瞬间变大,不会有动画)
852+
updateActive();
853+
854+
// 5. 强制浏览器重绘 (Force Reflow)
855+
// 告诉浏览器:“立刻把上面的变化渲染出来,不要等”
856+
void track.offsetHeight;
857+
858+
// 6. 恢复动画能力 (下一帧再移除锁,确保万无一失)
859+
requestAnimationFrame(() => {
860+
allCards.forEach(c => c.classList.remove('no-transition'));
861+
track.style.transition = '';
862+
});
863+
// === 核心修复结束 ===
875864
}
876865
});
877-
}
878866

879-
// 初始化
880-
updateTrack(false);
881-
updateActive();
867+
// 7. 事件绑定
868+
nextBtn.onclick = (e) => { e.preventDefault(); switchSlide(1); };
869+
prevBtn.onclick = (e) => { e.preventDefault(); switchSlide(-1); };
870+
871+
// 手机滑动
872+
let touchStartX = 0;
873+
track.addEventListener('touchstart', e => { touchStartX = e.changedTouches[0].screenX; }, {passive: true});
874+
track.addEventListener('touchend', e => {
875+
const diff = e.changedTouches[0].screenX - touchStartX;
876+
if (diff < -50) switchSlide(1);
877+
if (diff > 50) switchSlide(-1);
878+
}, {passive: true});
879+
880+
// 初始化
881+
updateTrack(false);
882+
updateActive();
883+
window.addEventListener('resize', () => updateTrack(false));
884+
}
882885

883-
// 监听窗口大小变化(自动适应横竖屏)
884-
window.addEventListener('resize', () => updateTrack(false));
886+
window.addEventListener('load', () => {
887+
try { initCarousel(); } catch(e) { console.error(e); }
888+
});

0 commit comments

Comments
 (0)