如何实现抖音 '回到刚刚查看的' 位置

实现效果,假如当前浏览的是第121条视频

 

首先需要先准备要用到的工具函数,和模拟接口回调函数

api.js

//模拟根据当前 id返回 该id前面有多少条视频
export function getOffset(id) {
    return new Promise(resolve => {
        resolve(121)
    })
}



//模拟返回一个具有分页功能的图片列表的函数
export function getVideo(pageNo, pageSize) {
    return new Promise(resolve => {
        // let page = Math.ceil(pageNo/pageSize) //Calculates the number of pages.
        let start = (pageNo - 1) * pageSize; //Calculates the start index. 
        let end = start + pageSize; //Calculates the end index. 
        let arr = []; //Declaring an array. 

        for (let i = start; i < end; i++) { //For loop to add the image to the array. 
            let obj = { id: i, cover: `https://picsum.photos/200/${i}` }
            arr.push(obj)

        }
        resolve(arr) //Resolve the promise.
    })
}

工具函数

utils.js


//防抖
export function debounce(fn, delay = 500) {
    let timer = null;

    return function () {
        if (timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(() => {
            fn.apply(this, arguments)
            timer = null
        }, delay)
    }

}

//根据页码,和数量获取到当前 对应的下标范围
export function getIndexRange(pageNo, pageSize) {
    const start = (pageNo - 1) * pageSize; // -1 to compensate for 0 indexing.  0-0 is 0.  So -
    const end = start + pageSize - 1; // compensate for 0-0 being 0.  So end is the same as start.  So end = start.  So
    return [start, end]

}

//根据下标,和数量,计算出在哪一页
export function getPage(index, size) {
    return Math.ceil((index + 1) / size)
}

 

主要运用逻辑在两个函数

createElement 动态创建列表节点
loadPages  加载对应索引页的内容
 
主函数页面
import { getOffset, getVideo } from './api.js' 
import { debounce, getIndexRange, getPage } from './utils.js'

const currentId = 100 //当前看过视频的id
const SIZE = 10  //分页的size
const continer = document.querySelector('.continer')
const indicator = document.querySelector('.indicator')


let visibleIndex = new Set() //用于存放出现在视口中元素的 索引值

const ob = new IntersectionObserver(entires => {    //创建一个元素监听器,监听 每个视频的item

    for(const entry of entires){
        const index = +entry.target.dataset.index;
        if(entry.isIntersecting){    // isIntersecting:表示这个元素是否出现在视口中
            visibleIndex.add(index)
        } else {
            visibleIndex.delete(index)
        }
    }
    loadPagesDebounce() //加载这些索引对应的内容
    
})

function getRange() {  //获取视口中索引值集合的 最大和最小
    if(visibleIndex.size === 0) return [0,0]
    const min = Math.min(...visibleIndex)
    const max = Math.max(...visibleIndex)
    return [min, max]
}

function createElement(page) {   //手动创建 需要加载的所有节点
    const childrenLen = continer.children.length
    const count = page * SIZE - childrenLen;
    for (let i = 0; i < count; i++) {
        const item = document.createElement('div')
        item.className = 'item'
        item.dataset.index = i + childrenLen
        continer.appendChild(item)
        ob.observe(item)  //为所有节点添加监听器
    }

}

function loadPages(){
    //得到当前可以看到的索引范围的内容
    const [minIndex, maxIndex] = getRange()
    const pages = new Set()
    for (let i = minIndex; i <= maxIndex; i++) {
        pages.add(getPage(i, SIZE))  //获取当前视口中需要加载的页面集合pages
    }
    for (const page of pages) {
        const [minIndex, maxIndex] = getIndexRange(page, SIZE)
        //   console.log(minIndex, maxIndex)
        if (continer.children[minIndex].dataset.loaded) { //防止已经加载过的重复加载
            continue;
        }
        continer.children[minIndex].dataset.loaded = true

        getVideo(page, SIZE).then(async (res) => {   //循坏模拟获取数据的列表

            for (let i = minIndex; i <= maxIndex; i++) {
                const item = continer.children[i] 	//item is a div element.
                const offset = await getOffset(currentId)
                item.innerHTML = `
                    <img src="${res[i - minIndex].cover}"  />
                    <span style='color:#f00'>${i} ${item.dataset.index == offset ? '---刚刚看过!' : ''}</span>
                `
            }
        })
    }
    setIndicatorVisible()

}

//判断当前位置是否需要展示刚刚看过的按钮
async function setIndicatorVisible(){
    const offset = await getOffset(currentId)
    const [minIndex, maxIndex] = getRange() 	//currentId is a div element.  offset is the top left of the element.  (0,0) is the top left of
    const page = getPage(offset, SIZE)
    // console.log(minIndex, maxIndex, offset)
    if(offset>=minIndex && offset<=maxIndex){
     indicator.style.display = 'none'	//display the indicator element.  (the div element)  (the top left of the element)  (0,
    } else indicator.style.display = 'block'	
 
    indicator.dataset.page = page
    indicator.dataset.index = offset
 }

indicator.onclick = () => {
  const page = +indicator.dataset.page 	//page is a number.  + indicator.dataset.page is a string.  + indicator
  const index = +indicator.dataset.index 	//index is a number.  + indicator.dataset.index is a string
  createElement(page)
  continer.children[index].scrollIntoView({
    behavior:'smooth',
    block:'center'
  })
}

const loadPagesDebounce = debounce(loadPages, 300)

createElement(1) //首次先加载一页
 
html代码
<!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">
    <title>Document</title>
</head>
<style>
    /* body,html{
        position: relative;
    } */
    .continer{
        display: grid;
        /* grid-gap: 20px; */
        grid-template-columns: 1fr 1fr 1fr;
        grid-column-gap: 10px;
        grid-row-gap: 15px;
        /* flex-wrap: wrap; */
        padding: 0 200px;
        box-sizing: border-box;
    }
    .item{
        width: 200px;
        height: 300px;
        border: 1px solid #000;
        margin-bottom: 20px;
        padding-bottom: 20px;
    }
    .item img{
        width: 200px;
        height: 280px;
        object-fit: cover;
    }
    .indicator{
        position: fixed;
        left: 0;
        right: 0;
        bottom: 20px;
        width: 100px;
        margin: 0 auto;
        text-align: center;
        /* display: none; */
        background: #f00;
        color: #fff; /* for IE */
    }
</style>
<body>
    <div class="continer">
        
    </div>
   <div class="indicator">前往刚刚看过</div>
</body>
<script type="module" src="./loadPages/index.js"></script>
<script type="module" src="./loadPages/api.js"></script>
</html>

 

posted @ 2023-06-02 17:46  10后程序员劝退师  阅读(152)  评论(0编辑  收藏  举报