Skip to content

Commit 4b44703

Browse files
committed
minor
1 parent 6f0bb1a commit 4b44703

File tree

2 files changed

+131
-124
lines changed

2 files changed

+131
-124
lines changed

assets/css/style.css

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -917,39 +917,39 @@ h1, h2, h3, p, li, .caption, .small {
917917
.next-btn{ right: 8px; }
918918
}
919919

920-
921920
.carousel-container {
922921
position: relative;
923922
max-width: 1000px;
924923
margin: 20px auto;
925-
overflow: hidden; /* 防止溢出 */
924+
overflow: hidden;
925+
user-select: none; /* 禁止选中文本 */
926+
-webkit-user-select: none;
926927
}
927928

928929
.carousel-window {
929930
width: 100%;
930931
position: relative;
931-
/* 边缘遮罩,让切断处更自然 */
932+
/* 蒙版让边缘融合 */
932933
-webkit-mask-image: linear-gradient(to right, transparent 0%, black 5%, black 95%, transparent 100%);
933934
mask-image: linear-gradient(to right, transparent 0%, black 5%, black 95%, transparent 100%);
934935
}
935936

936937
.carousel-track {
937938
display: flex;
938-
gap: 15px; /* 稍微减小间距 */
939+
gap: 15px;
939940
width: 100%;
940941
padding: 10px 0;
942+
cursor: grab;
943+
/* 启用硬件加速 */
944+
transform: translate3d(0, 0, 0);
941945
}
942946

943947
.carousel-card {
944-
/* 默认 PC 端逻辑 */
945948
flex: 0 0 60%;
946949
max-width: 60%;
947-
948-
transition: transform 0.5s cubic-bezier(0.25, 1, 0.5, 1), opacity 0.5s ease;
950+
transition: transform 0.4s ease-out, opacity 0.4s ease-out; /* 稍快一点 */
949951
opacity: 0.3;
950952
transform: scale(0.9);
951-
filter: blur(0px); /* 手机上尽量少用 blur,这会消耗大量 GPU */
952-
953953
display: flex;
954954
flex-direction: column;
955955
align-items: center;
@@ -960,20 +960,17 @@ h1, h2, h3, p, li, .caption, .small {
960960
/* --- 手机端适配 --- */
961961
@media (max-width: 768px) {
962962
.carousel-card {
963-
/* 手机上卡片宽一点,看视频更清楚 */
964-
flex: 0 0 80%;
965-
max-width: 80%;
963+
flex: 0 0 75%; /* 稍微改小一点点,留出缝隙给按钮 */
964+
max-width: 75%;
966965
}
967966
}
968967

969-
/* 激活状态 */
970968
.carousel-card.is-active {
971969
opacity: 1;
972970
transform: scale(1);
973971
z-index: 10;
974972
}
975973

976-
/* 禁止动画的类(用于瞬移) */
977974
.no-transition, .no-transition * {
978975
transition: none !important;
979976
}
@@ -985,18 +982,18 @@ h1, h2, h3, p, li, .caption, .small {
985982
overflow: hidden;
986983
background: #000;
987984
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
988-
/* 硬件加速,防止手机闪烁 */
989-
transform: translateZ(0);
990-
will-change: transform;
985+
transform: translateZ(0);
986+
987+
/* === 核心修复 === */
988+
/* 彻底禁止视频层接收点击,确保点击直接穿透到下层或不挡住上层 */
989+
pointer-events: none;
991990
}
992991

993992
.video-wrapper video {
994993
width: 100%;
995994
height: 100%;
996995
object-fit: cover;
997996
display: block;
998-
/* 确保视频在手机上不全屏播放 */
999-
pointer-events: none;
1000997
}
1001998

1002999
.caption {
@@ -1008,29 +1005,33 @@ h1, h2, h3, p, li, .caption, .small {
10081005
opacity: 0;
10091006
transition: opacity 0.3s;
10101007
}
1008+
.carousel-card.is-active .caption { opacity: 1; }
10111009

1012-
.carousel-card.is-active .caption {
1013-
opacity: 1;
1014-
}
1015-
1010+
/* === 箭头修复 === */
10161011
.nav-btn {
10171012
position: absolute;
10181013
top: 50%;
10191014
transform: translateY(-50%);
1020-
width: 40px; /* 手机上按钮稍微小一点 */
1021-
height: 40px;
1015+
width: 50px; /* 加大尺寸 */
1016+
height: 50px;
10221017
border-radius: 50%;
1023-
background: rgba(255, 255, 255, 0.9);
1024-
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
1018+
background: rgba(255, 255, 255, 0.95);
1019+
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
10251020
cursor: pointer;
1026-
z-index: 20;
1021+
1022+
/* === 核弹级层级 === */
1023+
z-index: 9999; /* 保证没有任何东西能盖住它 */
1024+
10271025
display: flex;
10281026
align-items: center;
10291027
justify-content: center;
10301028
color: #333;
10311029
border: none;
1032-
/* 移除手机点击的高亮色块 */
1033-
-webkit-tap-highlight-color: transparent;
1030+
-webkit-tap-highlight-color: transparent; /* 去掉手机点击蓝框 */
1031+
1032+
/* 确保按钮自己可以被点 */
1033+
pointer-events: auto;
10341034
}
1035+
10351036
.prev-btn { left: 10px; }
10361037
.next-btn { right: 10px; }

assets/js/main.js

Lines changed: 100 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -742,135 +742,141 @@ function initDemoRotator(){
742742
}
743743

744744
window.addEventListener('load', ()=>{ try{ initDemoRotator(); }catch(e){} });
745+
// 1. 初始化变量
745746
const track = document.getElementById('track');
746-
let cards = document.querySelectorAll('.carousel-card');
747+
const originalCards = document.querySelectorAll('.carousel-card');
747748
const prevBtn = document.getElementById('prevBtn');
748749
const nextBtn = document.getElementById('nextBtn');
750+
const gapPx = 15;
749751

750-
// 配置
751-
const cardWidthPercent = 60;
752-
const gapPx = 20;
752+
// 动态计算宽度百分比
753+
function getCardWidthPercent() {
754+
return window.innerWidth < 768 ? 75 : 60;
755+
}
753756

754-
// 1. 克隆首尾
755-
const firstClone = cards[0].cloneNode(true);
756-
const lastClone = cards[cards.length - 1].cloneNode(true);
757+
// 2. 克隆卡片
758+
const firstClone = originalCards[0].cloneNode(true);
759+
const lastClone = originalCards[originalCards.length - 1].cloneNode(true);
757760
track.appendChild(firstClone);
758-
track.insertBefore(lastClone, cards[0]);
761+
track.insertBefore(lastClone, originalCards[0]);
759762

760-
// 重新获取列表
761763
let allCards = document.querySelectorAll('.carousel-card');
762-
let currentIndex = 1;
764+
let currentIndex = 1;
763765
let isTransitioning = false;
764766

765-
// 初始化
766-
updateTrack(false);
767-
updateActive();
768-
769-
// --- 按钮事件 ---
770-
nextBtn.addEventListener('click', () => {
767+
// 3. 核心切换函数
768+
function switchSlide(direction) {
771769
if (isTransitioning) return;
772-
if (currentIndex >= allCards.length - 1) return; // 保护
773770

774-
currentIndex++;
775-
isTransitioning = true;
776-
updateTrack(true);
777-
updateActive();
778-
});
779-
780-
prevBtn.addEventListener('click', () => {
781-
if (isTransitioning) return;
782-
if (currentIndex <= 0) return; // 保护
771+
// 边界保护
772+
if (direction === 1 && currentIndex >= allCards.length - 1) return;
773+
if (direction === -1 && currentIndex <= 0) return;
783774

784-
currentIndex--;
775+
currentIndex += direction;
785776
isTransitioning = true;
786777
updateTrack(true);
787778
updateActive();
788-
});
789-
790-
// --- 关键:过渡结束后的无缝重置 ---
791-
track.addEventListener('transitionend', () => {
792-
isTransitioning = false;
779+
}
793780

794-
const totalRealCards = allCards.length - 2; // 3张
795-
let needsReset = false;
796-
let newIndex = currentIndex;
797-
798-
// 判断是否到了克隆边缘
799-
if (currentIndex === allCards.length - 1) {
800-
newIndex = 1; // 回到真实第1张
801-
needsReset = true;
802-
} else if (currentIndex === 0) {
803-
newIndex = totalRealCards; // 回到真实最后一张
804-
needsReset = true;
805-
}
781+
// === 4. 事件绑定 (暴力修复点击问题) ===
806782

807-
if (needsReset) {
808-
// 执行无缝替换
809-
handleReset(newIndex);
810-
}
783+
// 绑定点击
784+
nextBtn.addEventListener('click', (e) => {
785+
e.preventDefault();
786+
switchSlide(1);
787+
});
788+
prevBtn.addEventListener('click', (e) => {
789+
e.preventDefault();
790+
switchSlide(-1);
811791
});
812792

813-
// --- 核心修复函数:处理重置 ---
814-
function handleReset(targetIndex) {
815-
// 1. 获取当前显示的克隆卡片 和 目标真身卡片
816-
const currentCloneCard = allCards[currentIndex];
817-
const targetRealCard = allCards[targetIndex];
793+
// 绑定触摸结束 (针对部分安卓机)
794+
// 为了防止 Click 和 Touchend 触发两次,我们加个简单的锁
795+
let touchLock = false;
796+
function handleTouchBtn(e, dir) {
797+
e.preventDefault(); // 防止触发后面的 click
798+
e.stopPropagation(); // 防止冒泡
799+
if(touchLock) return;
818800

819-
// 2. 视频时间同步!(关键修复点)
820-
// 把克隆视频的播放时间,赋给真身视频,保证画面不跳变
821-
const cloneVideo = currentCloneCard.querySelector('video');
822-
const realVideo = targetRealCard.querySelector('video');
823-
if (cloneVideo && realVideo) {
824-
realVideo.currentTime = cloneVideo.currentTime;
825-
if (!cloneVideo.paused) realVideo.play();
826-
}
801+
switchSlide(dir);
802+
803+
touchLock = true;
804+
setTimeout(() => { touchLock = false; }, 300);
805+
}
827806

828-
// 3. 冻结动画!(关键修复点)
829-
// 给 Track 和所有卡片加上 .no-transition,禁止任何 CSS 渐变
830-
track.classList.add('no-transition');
831-
allCards.forEach(c => c.classList.add('no-transition'));
807+
nextBtn.addEventListener('touchend', (e) => handleTouchBtn(e, 1));
808+
prevBtn.addEventListener('touchend', (e) => handleTouchBtn(e, -1));
832809

833-
// 4. 瞬间改变索引和位置
834-
currentIndex = targetIndex;
835-
updateTrack(false);
836-
837-
// 5. 瞬间更新 Active 状态
838-
// 此时因为有 no-transition,scale(1) 和 opacity(1) 会立即生效,不会有过渡
839-
updateActive();
840810

841-
// 6. 强制浏览器重绘 (Reflow)
842-
// 这一行非常重要,它迫使浏览器立刻应用上面的改变
843-
void track.offsetHeight;
811+
// 5. 触摸滑动支持 (Swipe)
812+
let touchStartX = 0;
813+
track.addEventListener('touchstart', e => { touchStartX = e.changedTouches[0].screenX; }, {passive: true});
814+
track.addEventListener('touchend', e => {
815+
const touchEndX = e.changedTouches[0].screenX;
816+
if (touchEndX < touchStartX - 50) switchSlide(1);
817+
if (touchEndX > touchStartX + 50) switchSlide(-1);
818+
}, {passive: true});
844819

845-
// 7. 恢复动画
846-
// 使用 requestAnimationFrame 在下一帧移除 no-transition
847-
requestAnimationFrame(() => {
848-
track.classList.remove('no-transition');
849-
allCards.forEach(c => c.classList.remove('no-transition'));
850-
});
851-
}
852820

853-
// --- 基础更新函数 ---
854-
function updateTrack(animate) {
855-
if (animate) {
856-
track.style.transition = 'transform 0.5s cubic-bezier(0.25, 1, 0.5, 1)';
857-
} else {
858-
track.style.transition = 'none';
821+
// 6. 无缝循环处理
822+
track.addEventListener('transitionend', () => {
823+
isTransitioning = false;
824+
let targetIndex = -1;
825+
826+
if (currentIndex === allCards.length - 1) targetIndex = 1;
827+
else if (currentIndex === 0) targetIndex = allCards.length - 2;
828+
829+
if (targetIndex !== -1) {
830+
// 视频同步
831+
const v1 = allCards[currentIndex].querySelector('video');
832+
const v2 = allCards[targetIndex].querySelector('video');
833+
if (v1 && v2) v2.currentTime = v1.currentTime;
834+
835+
// 瞬移
836+
track.classList.add('no-transition');
837+
allCards.forEach(c => c.classList.add('no-transition'));
838+
839+
currentIndex = targetIndex;
840+
updateTrack(false);
841+
updateActive();
842+
843+
void track.offsetHeight; // 强制重绘
844+
requestAnimationFrame(() => {
845+
track.classList.remove('no-transition');
846+
allCards.forEach(c => c.classList.remove('no-transition'));
847+
});
859848
}
860-
const centerOffset = 20;
861-
const val = `calc(${centerOffset}% - ${currentIndex} * (${cardWidthPercent}% + ${gapPx}px))`;
849+
});
850+
851+
// 7. 更新视图逻辑
852+
function updateTrack(animate) {
853+
if (animate) track.style.transition = 'transform 0.4s ease-out';
854+
else track.style.transition = 'none';
855+
856+
const wPercent = getCardWidthPercent();
857+
const centerOffset = (100 - wPercent) / 2;
858+
const val = `calc(${centerOffset}% - ${currentIndex} * (${wPercent}% + ${gapPx}px))`;
862859
track.style.transform = `translateX(${val})`;
863860
}
864861

865862
function updateActive() {
866863
allCards.forEach((card, index) => {
864+
const v = card.querySelector('video');
867865
if (index === currentIndex) {
868866
card.classList.add('is-active');
869-
// 确保播放
870-
const v = card.querySelector('video');
871-
if(v) v.play();
867+
if (v) {
868+
v.muted = true;
869+
v.playsInline = true;
870+
v.play().catch(()=>{});
871+
}
872872
} else {
873873
card.classList.remove('is-active');
874+
if (v) v.pause();
874875
}
875876
});
876-
}
877+
}
878+
879+
// 初始化
880+
updateTrack(false);
881+
updateActive();
882+
window.addEventListener('resize', () => updateTrack(false));

0 commit comments

Comments
 (0)