SOURCE

console 命令行工具 X clear

                    
>
console
<!-- https://juejin.cn/post/7054088878877048869?searchId=2023081316160731123206B57E1C58A381 -->
<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8" />
	<meta http-equiv="X-UA-Compatible" content="IE=edge" />
	<meta name="viewport" content="width=device-width, initial-scale=1.0" />
	<script src="https://unpkg.com/vue@3.2.27/dist/vue.global.js">
	</script>
	<title>VirtualScroll</title>
</head>

<body>
	<div id="app">
		<div ref="demo" class="scroll-box demo" :style="`height: ${showNumber * itemHeight}px;`">
			<div class="scroll-blank" :style="`height: ${data.length * itemHeight}px;`"></div>
			<div class="scroll-data" :style="`top: ${positionTop}px;`">
				<div v-for="(item, index) in activeList" :key="item" class="scroll-item">
					{{ item }}
				</div>
			</div>
		</div>
	</div>
	<script>
		const { computed, onMounted, onUnmounted, ref } = Vue

      const createData = (length) => {
        return Object.keys(new Array(length).fill(''))
      }
      const App = {
        setup() {
          const demo = ref(null) // 外框盒子
          const showNumber = 20 // 当前视窗展示条数
          const itemHeight = 20 // 每一条内容的高度
          const data = createData(1000) // 实际数据
          let startNum = ref(0) // 当前视窗范围内第一个元素下标
          let positionTop = ref(0) // 当前视窗范围内第一个元素偏移量

          // 计算当前视窗内实际要渲染的内容
          const activeList = computed(() => {
            const start = startNum.value
            return data.slice(start, start + showNumber)
          })

          // 滚动的时候计算当前视窗范围内第一个元素下标
          const scrollEvent = (event) => {
            const { scrollTop } = event.target
            startNum.value = parseInt(scrollTop / itemHeight)
            positionTop.value = scrollTop
            console.log('-----------');
          }

          onMounted(() => {
            demo.value.addEventListener('scroll', throttle(scrollEvent, 100))
          })
          onUnmounted(() => {
            if (!demo.value) return
            demo.value.removeEventListener('scroll', scrollEvent)
            demo.value = null
          })
          
          // 节流
          function throttle(fn, delay) {
            let timer = null; // 定时器
            let startTime = Date.now(); // 初始化当前时间,用于记录上一次函数执行的时间
            return function () { // 返回一个函数,形成闭包,保证每次执行函数时,上一次的执行时间和定时器都在内存中
                const currentTime = Date.now(); // 记录当前时间
                const remaining = delay - (currentTime - startTime); // 计算剩余时间,比较上次到现在经历了多久
                const context = this; // 记录当前的上下文环境,用来执行传入的函数fn
                const args = arguments;
                clearTimeout(timer); // 清除之前的定时器,避免重复执行
                if (remaining <= 0) { // 根据 remaining 的值来判断是否立即执行回调函数fn 或者 延迟执行
                    fn.apply(context, args); // 立即执行回调函数fn,并传入之前记录的上下文和参数
                    startTime = Date.now(); // 重置开始时间为当前时间
                } else {
                    timer = setTimeout(fn, remaining); // 重新设置定时器,延迟时间为剩余时间 remaining
                }
            }
        }

          return {
            showNumber,
            itemHeight,
            demo,
            positionTop,
            data,
            activeList,
          }
        },
      }

      const app = Vue.createApp(App)
      app.mount('#app')
	</script>
	<style>
		.scroll-box {
			position: relative;
			overflow: auto;
			width: 400px;
			border: 1px solid rgb(0, 0, 0);
		}

		.scroll-data {
			position: absolute;
			width: 100%;
		}

		.scroll-item {
			height: 20px;
		}

		.scroll-item:hover {
			background: rgb(104, 111, 211);
			color: #fff;
		}
	</style>
</body>

</html>