296 lines
8.2 KiB
HTML
296 lines
8.2 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>视频圆形放大镜(最终流畅版)</title>
|
||
<script src="./lazyload.min.js"></script>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
list-style: none;
|
||
font-size: 0;
|
||
line-height: 0;
|
||
background: #000;
|
||
}
|
||
|
||
ul {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
ul li {
|
||
width: 20%;
|
||
background: #000;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
ul li .lazy {
|
||
display: block;
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
/* 放大镜样式 */
|
||
.video-magnifier {
|
||
position: fixed;
|
||
width: 400px;
|
||
height: 400px;
|
||
border-radius: 50%;
|
||
border: 3px solid rgba(255, 255, 255, 0.9);
|
||
box-shadow: 0 0 30px rgba(0, 0, 0, 0.6), 0 0 0 4px rgba(0, 0, 0, 0.3);
|
||
pointer-events: none;
|
||
overflow: hidden;
|
||
z-index: 10000;
|
||
display: none;
|
||
backdrop-filter: blur(10px);
|
||
transition: left 0.02s ease-out, top 0.02s ease-out;
|
||
background: transparent !important;
|
||
}
|
||
|
||
.video-magnifier video {
|
||
position: absolute;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: transparent !important;
|
||
}
|
||
</style>
|
||
</head>
|
||
|
||
<body>
|
||
<ul></ul>
|
||
<!-- 放大镜容器 -->
|
||
<div class="video-magnifier" id="magnifier">
|
||
<video id="magnifierVideo" muted loop playsinline></video>
|
||
</div>
|
||
|
||
<script>
|
||
let page = "";
|
||
const defaultSpeed = 2;
|
||
let scrollSpeed = defaultSpeed;
|
||
let scrollLock = false;
|
||
let handlerId = null;
|
||
let currentVideo = null;
|
||
const ul = document.querySelector("ul");
|
||
|
||
const magnifier = document.getElementById('magnifier');
|
||
const magnifierVideo = document.getElementById('magnifierVideo');
|
||
let hoveredVideo = null;
|
||
let magnifierEnabled = false;
|
||
|
||
// ==================== 流畅缩放配置 ====================
|
||
const minMagnifierSize = 400; // 最小尺寸
|
||
const maxMagnifierSize = 600; // 最大尺寸(防卡顿)
|
||
const stepSize = 100; // 每次增减 40px(流畅不卡)
|
||
let magnifierSize = 400;
|
||
const scale = 4;
|
||
|
||
// 记录当前鼠标位置
|
||
let currentMouseX = 0;
|
||
let currentMouseY = 0;
|
||
|
||
let lazyLoadInstance = null;
|
||
|
||
// 实时记录鼠标位置
|
||
document.addEventListener('mousemove', (e) => {
|
||
currentMouseX = e.clientX;
|
||
currentMouseY = e.clientY;
|
||
});
|
||
|
||
const initLazyLoad = () => {
|
||
if (lazyLoadInstance) return;
|
||
lazyLoadInstance = new LazyLoad({
|
||
threshold: 300,
|
||
elements_selector: ".lazy",
|
||
unobserve_entered: true,
|
||
cancel_on_exit: true,
|
||
});
|
||
};
|
||
|
||
const getHttpData = async () => {
|
||
if (scrollLock) return;
|
||
try {
|
||
scrollLock = true;
|
||
const res = await fetch(`http://127.0.0.1:3030?pcursor=${page}`);
|
||
const data = await res.json();
|
||
scrollLock = false;
|
||
render(data);
|
||
} catch (err) {
|
||
scrollLock = false;
|
||
console.error("数据加载失败", err);
|
||
}
|
||
};
|
||
|
||
const render = (data) => {
|
||
const { feeds, pcursor } = data;
|
||
page = pcursor;
|
||
if (!feeds || feeds.length === 0) return;
|
||
|
||
const fragment = document.createDocumentFragment();
|
||
feeds.forEach(item => {
|
||
const li = document.createElement("li");
|
||
const video = document.createElement("video");
|
||
|
||
video.className = "lazy";
|
||
video.preload = "metadata";
|
||
video.muted = true;
|
||
video.loop = true;
|
||
video.playsInline = true;
|
||
video.author = `https://www.kuaishou.com/profile/${item.author.id}`;
|
||
|
||
const posterUrl = item.photo.animatedCoverUrl || item.photo.coverUrl;
|
||
video.poster = posterUrl;
|
||
video.dataset.src = item.photo.photoUrls[0]?.url || "";
|
||
|
||
li.appendChild(video);
|
||
fragment.appendChild(li);
|
||
});
|
||
|
||
ul.appendChild(fragment);
|
||
if (lazyLoadInstance) lazyLoadInstance.update();
|
||
if (!handlerId) startScroll();
|
||
};
|
||
|
||
const startScroll = () => {
|
||
const scrollStep = () => {
|
||
window.scrollBy(0, scrollSpeed);
|
||
handlerId = requestAnimationFrame(scrollStep);
|
||
};
|
||
handlerId = requestAnimationFrame(scrollStep);
|
||
};
|
||
|
||
const stopScroll = () => {
|
||
cancelAnimationFrame(handlerId);
|
||
handlerId = null;
|
||
};
|
||
|
||
const toggleScroll = () => {
|
||
handlerId ? stopScroll() : startScroll();
|
||
};
|
||
|
||
document.onclick = toggleScroll;
|
||
|
||
// 强制更新放大镜到当前鼠标位置
|
||
function updateMagnifierToMouse() {
|
||
if (!magnifierEnabled) return;
|
||
|
||
magnifier.style.width = magnifierSize + 'px';
|
||
magnifier.style.height = magnifierSize + 'px';
|
||
|
||
const element = document.elementFromPoint(currentMouseX, currentMouseY);
|
||
if (!element || element.tagName !== 'VIDEO') {
|
||
magnifier.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
const video = element;
|
||
if (hoveredVideo !== video) {
|
||
hoveredVideo = video;
|
||
magnifierVideo.src = video.dataset.src || video.src;
|
||
magnifierVideo.play().catch(() => { });
|
||
}
|
||
|
||
magnifier.style.display = 'block';
|
||
magnifier.style.left = (currentMouseX - magnifierSize / 2) + 'px';
|
||
magnifier.style.top = (currentMouseY - magnifierSize / 2) + 'px';
|
||
|
||
const rect = video.getBoundingClientRect();
|
||
const relativeX = (currentMouseX - rect.left) / rect.width;
|
||
const relativeY = (currentMouseY - rect.top) / rect.height;
|
||
const offsetX = -(relativeX - 0.5) * rect.width * scale;
|
||
const offsetY = -(relativeY - 0.5) * rect.height * scale;
|
||
magnifierVideo.style.transform = `scale(${scale}) translate(${offsetX}px, ${offsetY}px)`;
|
||
}
|
||
|
||
document.onkeydown = (e) => {
|
||
const { keyCode } = e;
|
||
|
||
// F键:开关放大镜
|
||
if (keyCode === 70) {
|
||
e.preventDefault();
|
||
magnifierEnabled = !magnifierEnabled;
|
||
if (magnifierEnabled) {
|
||
updateMagnifierToMouse();
|
||
} else {
|
||
magnifier.style.display = 'none';
|
||
magnifierVideo.pause();
|
||
magnifierVideo.src = '';
|
||
hoveredVideo = null;
|
||
}
|
||
}
|
||
|
||
// + 键:每次增加 40px,最大 800px
|
||
if (keyCode === 107 || keyCode === 187) {
|
||
e.preventDefault();
|
||
if (magnifierSize < maxMagnifierSize) {
|
||
magnifierSize += stepSize;
|
||
updateMagnifierToMouse();
|
||
}
|
||
}
|
||
|
||
// - 键:每次减少 40px,最小 400px
|
||
if (keyCode === 109 || keyCode === 189) {
|
||
e.preventDefault();
|
||
if (magnifierSize > minMagnifierSize) {
|
||
magnifierSize -= stepSize;
|
||
updateMagnifierToMouse();
|
||
}
|
||
}
|
||
|
||
// 其他原有按键
|
||
if (keyCode === 79) { e.preventDefault(); if (currentVideo) open(currentVideo.author); }
|
||
if (keyCode === 32) { e.preventDefault(); toggleScroll(); }
|
||
if (keyCode === 82) { e.preventDefault(); scrollSpeed = defaultSpeed; }
|
||
if (keyCode === 40) { e.preventDefault(); scrollSpeed += 2; }
|
||
if (keyCode === 38) { e.preventDefault(); scrollSpeed > defaultSpeed && (scrollSpeed -= 2); }
|
||
};
|
||
|
||
let scrollTimer = null;
|
||
window.onscroll = () => {
|
||
if (scrollLock) return;
|
||
clearTimeout(scrollTimer);
|
||
scrollTimer = setTimeout(() => {
|
||
const scrollTop = window.scrollY;
|
||
const clientHeight = window.innerHeight;
|
||
const scrollHeight = document.documentElement.scrollHeight;
|
||
if (clientHeight + scrollTop >= scrollHeight - 300) {
|
||
getHttpData();
|
||
}
|
||
}, 150);
|
||
};
|
||
|
||
ul.addEventListener('mousemove', (e) => {
|
||
if (!magnifierEnabled) return;
|
||
updateMagnifierToMouse();
|
||
});
|
||
|
||
ul.addEventListener('mouseleave', () => {
|
||
if (!magnifierEnabled) return;
|
||
magnifier.style.display = 'none';
|
||
magnifierVideo.pause();
|
||
magnifierVideo.src = '';
|
||
hoveredVideo = null;
|
||
}, true);
|
||
|
||
ul.addEventListener("mouseenter", (e) => {
|
||
if (e.target.tagName === "VIDEO") {
|
||
e.target.readyState >= 2 && e.target.play();
|
||
currentVideo = e.target;
|
||
}
|
||
}, true);
|
||
|
||
initLazyLoad();
|
||
getHttpData();
|
||
</script>
|
||
</body>
|
||
|
||
</html> |