Replace js by ts
This commit is contained in:
236
src-tauri/assets/viewer/visibility-manager.js
Normal file
236
src-tauri/assets/viewer/visibility-manager.js
Normal file
@@ -0,0 +1,236 @@
|
||||
/**
|
||||
* visibility-manager.js — Advanced visibility management with scroll prediction
|
||||
*
|
||||
* Uses IntersectionObserver for accurate page visibility detection
|
||||
* Implements scroll prediction for smooth pre-loading
|
||||
* Manages buffer zones intelligently
|
||||
*/
|
||||
|
||||
class VisibilityManager {
|
||||
constructor(container, pageElements, totalPages) {
|
||||
this.container = container;
|
||||
this.pageElements = pageElements;
|
||||
this.totalPages = totalPages;
|
||||
|
||||
// Visibility state
|
||||
this.visiblePages = new Set();
|
||||
this.bufferPages = new Set();
|
||||
this.observer = null;
|
||||
this.bufferSize = 2; // Minimum pages to buffer
|
||||
this.scrollHistory = [];
|
||||
this.maxScrollHistory = 10;
|
||||
|
||||
// Scroll prediction
|
||||
this.scrollVelocity = 0;
|
||||
this.scrollDirection = 0; // 1 for down, -1 for up, 0 for stationary
|
||||
this.lastScrollTime = 0;
|
||||
this.lastScrollPosition = 0;
|
||||
|
||||
// Performance tracking
|
||||
this.visibilityChanges = 0;
|
||||
this.lastVisibilityTime = 0;
|
||||
|
||||
// Setup observers
|
||||
this.setupIntersectionObserver();
|
||||
this.setupScrollMonitoring();
|
||||
}
|
||||
|
||||
setupIntersectionObserver() {
|
||||
this.observer = new IntersectionObserver(
|
||||
(entries) => this.handleIntersectionEntries(entries),
|
||||
{
|
||||
root: this.container,
|
||||
rootMargin: '100% 0px 100% 0px', // Large margin for extensive buffering
|
||||
threshold: [0, 0.25, 0.5, 0.75, 1.0] // Multiple thresholds for smooth transitions
|
||||
}
|
||||
);
|
||||
|
||||
// Observe all page elements
|
||||
this.pageElements.forEach(element => {
|
||||
this.observer.observe(element);
|
||||
});
|
||||
}
|
||||
|
||||
setupScrollMonitoring() {
|
||||
// Track scroll events for velocity calculation
|
||||
this.container.addEventListener('scroll', (event) => {
|
||||
this.trackScrollEvent(event);
|
||||
}, { passive: true });
|
||||
|
||||
// Periodic scroll prediction
|
||||
setInterval(() => {
|
||||
this.updateScrollPrediction();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
trackScrollEvent(event) {
|
||||
const currentTime = performance.now();
|
||||
const currentPosition = this.container.scrollTop;
|
||||
|
||||
// Calculate velocity if we have previous data
|
||||
if (this.lastScrollPosition !== 0 && this.lastScrollTime !== 0) {
|
||||
const timeDelta = currentTime - this.lastScrollTime;
|
||||
if (timeDelta > 0) {
|
||||
this.scrollVelocity = (currentPosition - this.lastScrollPosition) / timeDelta;
|
||||
this.scrollDirection = this.scrollVelocity > 0 ? 1 : (this.scrollVelocity < 0 ? -1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Store scroll history
|
||||
this.scrollHistory.push({
|
||||
position: currentPosition,
|
||||
time: currentTime,
|
||||
velocity: this.scrollVelocity
|
||||
});
|
||||
|
||||
if (this.scrollHistory.length > this.maxScrollHistory) {
|
||||
this.scrollHistory.shift();
|
||||
}
|
||||
|
||||
this.lastScrollPosition = currentPosition;
|
||||
this.lastScrollTime = currentTime;
|
||||
}
|
||||
|
||||
updateScrollPrediction() {
|
||||
if (this.scrollHistory.length < 3) return;
|
||||
|
||||
// Calculate average velocity from recent history
|
||||
const recentHistory = this.scrollHistory.slice(-5); // Last 5 entries
|
||||
if (recentHistory.length < 2) return;
|
||||
|
||||
const totalDelta = recentHistory[recentHistory.length - 1].position -
|
||||
recentHistory[0].position;
|
||||
const totalTime = recentHistory[recentHistory.length - 1].time -
|
||||
recentHistory[0].time;
|
||||
|
||||
if (totalTime > 0) {
|
||||
this.scrollVelocity = totalDelta / totalTime;
|
||||
this.scrollDirection = this.scrollVelocity > 5 ? 1 :
|
||||
(this.scrollVelocity < -5 ? -1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
handleIntersectionEntries(entries) {
|
||||
const newVisiblePages = new Set();
|
||||
const newBufferPages = new Set();
|
||||
|
||||
// Process intersection entries
|
||||
entries.forEach(entry => {
|
||||
const pageNum = parseInt(entry.target.dataset.page);
|
||||
if (isNaN(pageNum)) return;
|
||||
|
||||
if (entry.isIntersecting) {
|
||||
// Page is visible or in buffer zone
|
||||
if (entry.intersectionRatio >= 0.5) {
|
||||
newVisiblePages.add(pageNum);
|
||||
} else {
|
||||
newBufferPages.add(pageNum);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add intelligent buffering based on scroll prediction
|
||||
this.addPredictiveBuffering(newVisiblePages, newBufferPages);
|
||||
|
||||
// Update state if changed
|
||||
if (!this.setsEqual(this.visiblePages, newVisiblePages) ||
|
||||
!this.setsEqual(this.bufferPages, newBufferPages)) {
|
||||
|
||||
this.visiblePages = newVisiblePages;
|
||||
this.bufferPages = newBufferPages;
|
||||
this.visibilityChanges++;
|
||||
|
||||
// Notify about visibility changes
|
||||
this.notifyVisibilityChange();
|
||||
}
|
||||
}
|
||||
|
||||
addPredictiveBuffering(visiblePages, bufferPages) {
|
||||
if (visiblePages.size === 0) return;
|
||||
|
||||
const visibleArray = Array.from(visiblePages).sort((a, b) => a - b);
|
||||
const firstVisible = visibleArray[0];
|
||||
const lastVisible = visibleArray[visibleArray.length - 1];
|
||||
|
||||
// Add adjacent pages (minimum buffer)
|
||||
for (let i = 1; i <= this.bufferSize; i++) {
|
||||
if (firstVisible - i >= 1) bufferPages.add(firstVisible - i);
|
||||
if (lastVisible + i <= this.totalPages) bufferPages.add(lastVisible + i);
|
||||
}
|
||||
|
||||
// Add scroll prediction-based buffering
|
||||
if (Math.abs(this.scrollVelocity) > 10) { // Significant scrolling
|
||||
const predictionDistance = Math.min(5, Math.floor(Math.abs(this.scrollVelocity) / 2));
|
||||
|
||||
if (this.scrollDirection === 1) { // Scrolling down
|
||||
// Add more pages below
|
||||
for (let i = 1; i <= predictionDistance; i++) {
|
||||
const nextPage = lastVisible + this.bufferSize + i;
|
||||
if (nextPage <= this.totalPages) {
|
||||
bufferPages.add(nextPage);
|
||||
}
|
||||
}
|
||||
} else if (this.scrollDirection === -1) { // Scrolling up
|
||||
// Add more pages above
|
||||
for (let i = 1; i <= predictionDistance; i++) {
|
||||
const prevPage = firstVisible - this.bufferSize - i;
|
||||
if (prevPage >= 1) {
|
||||
bufferPages.add(prevPage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setsEqual(a, b) {
|
||||
if (a.size !== b.size) return false;
|
||||
return Array.from(a).every(item => b.has(item));
|
||||
}
|
||||
|
||||
notifyVisibilityChange() {
|
||||
// Throttle visibility notifications to prevent excessive rendering
|
||||
const now = performance.now();
|
||||
if (now - this.lastVisibilityTime < 50) return; // Max 20 updates per second
|
||||
|
||||
this.lastVisibilityTime = now;
|
||||
|
||||
// Notify the render system
|
||||
if (window.renderSystem) {
|
||||
window.renderSystem.setVisibility(
|
||||
Array.from(this.visiblePages),
|
||||
Array.from(this.bufferPages)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getScrollPrediction() {
|
||||
return {
|
||||
velocity: this.scrollVelocity,
|
||||
direction: this.scrollDirection,
|
||||
confidence: Math.min(1.0, Math.abs(this.scrollVelocity) / 20)
|
||||
};
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if (this.observer) {
|
||||
this.observer.disconnect();
|
||||
}
|
||||
|
||||
this.visiblePages.clear();
|
||||
this.bufferPages.clear();
|
||||
this.scrollHistory = [];
|
||||
}
|
||||
|
||||
getVisibilityStats() {
|
||||
return {
|
||||
visiblePages: Array.from(this.visiblePages).sort((a, b) => a - b),
|
||||
bufferPages: Array.from(this.bufferPages).sort((a, b) => a - b),
|
||||
visibilityChanges: this.visibilityChanges,
|
||||
scrollVelocity: this.scrollVelocity,
|
||||
scrollDirection: this.scrollDirection
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Export for ES6 modules
|
||||
export { VisibilityManager };
|
||||
Reference in New Issue
Block a user