UNPKG

mahmud-js-tools

Version:

一个简单而实用的JavaScript工具库,包含多种常用的工具函数。

254 lines (222 loc) 7.98 kB
/** * js-booster - High-performance frontend library * VirtualScroll - Virtual scrolling implementation * @version 1.1.1 * @license MIT */ class VirtualScroll { /** * Create a virtual scroll instance * @param {Object} options Configuration options * @param {HTMLElement} options.container Scroll container element * @param {Array} options.items Data items to display * @param {number} [options.itemHeight=20] Height of each list item (pixels) * @param {number} [options.bufferSize=10] Number of buffer items outside the visible area * @param {Function} [options.renderItem] Custom item rendering function * @param {Function} [options.renderHeader] Custom header rendering function * @param {number} [options.maxHeight=26840000] Maximum height in pixels for the content wrapper */ constructor(options) { this.container = options.container; this.items = options.items || []; this.itemHeight = options.itemHeight || 20; this.bufferSize = options.bufferSize || 10; this.customRenderItem = options.renderItem; this.customRenderHeader = options.renderHeader; this.maxHeight = options.maxHeight || 26840000; // Add maximum height limit to prevent DOM height overflow this.visibleStartIndex = 0; this.visibleEndIndex = 0; this.scrollContainer = null; this.contentWrapper = null; this.contentContainer = null; this.totalHeight = this.items.length * this.itemHeight; this.heightScale = 1; // Height scaling factor // If total height exceeds maximum height, calculate scaling factor if (this.totalHeight > this.maxHeight) { this.heightScale = this.maxHeight / this.totalHeight; } this.initialize(); } /** * Initialize virtual scroll component * @private */ initialize() { // Clear container this.container.innerHTML = ''; // Create scroll container this.scrollContainer = document.createElement('div'); // Add inline styles Object.assign(this.scrollContainer.style, { flex: '1', overflow: 'auto', position: 'relative', minHeight: '0', height: '100%', boxSizing: 'border-box' }); // If there's a custom header render function, render the header if (this.customRenderHeader) { const header = this.customRenderHeader(); if (header) { this.scrollContainer.appendChild(header); } } // Create content wrapper this.contentWrapper = document.createElement('div'); // Add inline styles Object.assign(this.contentWrapper.style, { position: 'relative', width: '100%' }); // Use scaled height to ensure it doesn't exceed browser limits const scaledHeight = this.totalHeight * this.heightScale; this.contentWrapper.style.height = `${scaledHeight}px`; // Create content container this.contentContainer = document.createElement('div'); // Add inline styles Object.assign(this.contentContainer.style, { position: 'absolute', width: '100%', left: '0' }); // Add scroll event listener this.scrollContainer.addEventListener('scroll', this.handleScroll.bind(this)); // Assemble DOM this.contentWrapper.appendChild(this.contentContainer); this.scrollContainer.appendChild(this.contentWrapper); this.container.appendChild(this.scrollContainer); // Render initial visible items this.renderVisibleItems(0, Math.min(100, this.items.length)); } /** * Handle scroll event * @private */ handleScroll() { const scrollTop = this.scrollContainer.scrollTop; const containerHeight = this.scrollContainer.clientHeight; // Consider scaling factor in calculations const realScrollTop = scrollTop / this.heightScale; // Calculate visible range const startIndex = Math.max(0, Math.floor(realScrollTop / this.itemHeight) - this.bufferSize); const endIndex = Math.min( this.items.length, Math.ceil((realScrollTop + containerHeight / this.heightScale) / this.itemHeight) + this.bufferSize ); // Only update when visible range changes if (startIndex !== this.visibleStartIndex || endIndex !== this.visibleEndIndex) { this.renderVisibleItems(startIndex, endIndex); this.visibleStartIndex = startIndex; this.visibleEndIndex = endIndex; } } /** * Render visible items * @param {number} startIndex Start index * @param {number} endIndex End index * @private */ renderVisibleItems(startIndex, endIndex) { // Clear content container this.contentContainer.innerHTML = ''; // Set position considering scaling factor this.contentContainer.style.transform = `translateY(${startIndex * this.itemHeight * this.heightScale}px)`; // Render visible items for (let i = startIndex; i < endIndex; i++) { const item = this.items[i]; if (this.customRenderItem) { // Use custom render function const itemElement = this.customRenderItem(item, i); if (itemElement) { // Only set necessary height styles, other styles are determined by the caller itemElement.style.height = `${this.itemHeight * this.heightScale}px`; itemElement.style.boxSizing = 'border-box'; itemElement.style.width = '100%'; this.contentContainer.appendChild(itemElement); } } else { // Use default rendering - very simple default implementation const row = document.createElement('div'); Object.assign(row.style, { height: `${this.itemHeight * this.heightScale}px`, width: '100%', boxSizing: 'border-box', padding: '8px', borderBottom: '1px solid #eee' }); row.textContent = JSON.stringify(item); this.contentContainer.appendChild(row); } } } /** * Update data items and re-render * @param {Array} items New data items array * @public */ updateItems(items) { this.items = items || []; this.totalHeight = this.items.length * this.itemHeight; // Recalculate scaling factor this.heightScale = 1; if (this.totalHeight > this.maxHeight) { this.heightScale = this.maxHeight / this.totalHeight; } // Ensure height is set correctly if (this.contentWrapper) { this.contentWrapper.style.height = `${this.totalHeight * this.heightScale}px`; } this.visibleStartIndex = 0; this.visibleEndIndex = 0; // Force recalculation of visible items this.handleScroll(); } /** * Scroll to specified index * @param {number} index Index of the item to scroll to * @public */ scrollToIndex(index) { if (index >= 0 && index < this.items.length) { // Apply scaling factor when scrolling this.scrollContainer.scrollTop = index * this.itemHeight * this.heightScale; } } /** * Destroy component, remove event listeners, etc. * @public */ destroy() { if (this.scrollContainer) { this.scrollContainer.removeEventListener('scroll', this.handleScroll); } if (this.container) { this.container.innerHTML = ''; } this.items = null; this.container = null; this.scrollContainer = null; this.contentWrapper = null; this.contentContainer = null; } /** * Refresh virtual scroll, re-render current visible items * @public */ refresh() { this.handleScroll(); } /** * Get scroll container element * @returns {HTMLElement} Scroll container element * @public */ getScrollContainer() { return this.scrollContainer; } } // Export VirtualScroll class // export { VirtualScroll }; // 文件最后保持这样 export default VirtualScroll;