欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

React?懸浮框內(nèi)容懶加載實(shí)例詳解

 更新時(shí)間:2022年11月16日 16:47:39   作者:Fantasy955  
這篇文章主要為大家介紹了React?懸浮框內(nèi)容懶加載實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

界面隱藏

一個(gè)容器放置視頻,默認(rèn)情況下

display: none;
z-index: 0;
transform: transform3d(10000px, true_y, true_z);

y軸和z軸左邊都是真實(shí)的(騰訊視頻使用絕對(duì)定位,因此是計(jì)算得到的),只是將其移到右邊很遠(yuǎn)的距離。

懶加載

React監(jiān)聽(tīng)鼠標(biāo)移入(獲取坐標(biāo))

  • 添加事件監(jiān)聽(tīng)
onMouseEnter={(e) => { handleMouseEnter(e) }}
const handleMouseEnter = (e: React.MouseEvent) => {
  console.log(e.target)
}

注意事件類型React.MouseEvent。

合成事件 – React (reactjs.org)

獲取Element的絕對(duì)位置

typescript中HTMLElement 和 Element的區(qū)別

ts中:

let res =document.getElementById('test');  //HTMLElement
let el = document.querySelector('#test');  // Element

mdn中: querySelectorgetElementById兩者均返回Element。

Element 是一個(gè)通用性非常強(qiáng)的基類,所有 Document 對(duì)象下的對(duì)象都繼承自它。這個(gè)接口描述了所有相同種類的元素所普遍具有的方法和屬性。一些接口繼承自 Element 并且增加了一些額外功能的接口描述了具體的行為。

例如, HTMLElement 接口是所有 HTML 元素的基本接口,而 SVGElement 接口是所有 SVG 元素的基礎(chǔ)。大多數(shù)功能是在這個(gè)類的更深層級(jí)(hierarchy)的接口中被進(jìn)一步制定的。

實(shí)現(xiàn):

function getElementAbsPos(e: HTMLElement) {
    var t = e!.offsetTop;
    var l = e!.offsetLeft;
    while (e = e!.offsetParent as HTMLElement) {
        t += e.offsetTop;
        l += e.offsetLeft;
    }
    return { left: l, top: t };
}

React實(shí)現(xiàn)

在騰訊視頻中,懸浮框是處于頂層div下的,因此使用絕對(duì)定位(絕對(duì)定位是相當(dāng)與父節(jié)點(diǎn)的,并不是document)。

在React中,由于我們將展示視頻信息的這個(gè)Item組件化了,因此實(shí)現(xiàn)思路有一點(diǎn)改變:

  • 每個(gè)Item都有一個(gè)對(duì)應(yīng)的懸浮框DIV,默認(rèn)情況hidden;
  • 為了節(jié)省流量,懸浮框內(nèi)的內(nèi)容需要懶加載;
  • 顯示懸浮框的時(shí)機(jī)是一致的——鼠標(biāo)移入時(shí),為了優(yōu)化體驗(yàn),節(jié)省流量,可以設(shè)定為移入一段時(shí)機(jī)后才顯示;

原始代碼

import { Card } from 'antd';
import { Content } from 'antd/lib/layout/layout';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom'
import styles from './css/VideoItem.module.css'
interface VideoItemProps {
    video: Video,
    topCategory?: string,
    subCategory?: string,
}
export default function VideoItem(props: VideoItemProps) {
    const { video, topCategory, subCategory } = props
    const navigate = useNavigate()
    const [loading, setLoding] = useState(false)
    const to = (() => {
        let itemTop = Object.getOwnPropertyNames(video.category)[0]
        let itemSub = video.category[itemTop].length ? video.category[itemTop][0] : ''
        if (topCategory) {
            itemTop = topCategory
            itemSub = ''
            if (subCategory) {
                itemSub = subCategory
            }
        }
        if (itemSub) {
            return `/detail/${itemTop}/${itemSub}/${video.id}`
        } else {
            return `/detail/${itemTop}/${video.id}`
        }
    })()
    return (
      <NavLink to={to}>
          <Card hoverable
              bordered={false}
              style={{ width: 180, height: 280, overflow: 'hidden' }}
              bodyStyle={{ padding: 4 }}
              className={styles.video}
              cover={<img style={{ width: '180px', height: '230px' }} alt={video.title} src={video.poster} />}
              onMouseOver={() => { }}
              onClick={() => handleClick()}
          >
              <div style={{ height: 280 }}>
                  {video.title}
              </div>
          </Card>
        </NavLink>
    )
}

handleClick響應(yīng)點(diǎn)擊事件,跳轉(zhuǎn)到視頻詳情頁(yè),以上代碼還不含與本文相關(guān)內(nèi)容。

放入新的DIV

 <NavLink to={to}>
     <Card
         bordered={false}
         bodyStyle={{ padding: 4 }}
         className={styles.video}
         cover={<img alt={video.title} src={video.poster} />}
     >
         <div className={styles.title}>
             {video.title}
         </div>
     </Card>
     <Card hoverable
         bordered={false}
         style={{
             backgroundColor: 'pink',
             display: hiddenDetail ? 'none' : 'inline-block',
             position: 'absolute',
             transform: `translate3d(0px, -100%, 0px)`,
         }}
         bodyStyle={{ padding: 4 }}
         className={styles.video}
         cover={<img alt={video.title} src={'占位圖鏈接'} />}
     >
         <div className={styles.title}>
             {video.title}
         </div>
     </Card>
</NavLink>

狀態(tài)設(shè)置

加入狀態(tài)表示是否隱藏懸浮框:

默認(rèn)隱藏

const [hiddenDetail, setHiddenDetail] = useState(true)

樣式設(shè)置

style={{
  backgroundColor: 'pink',
  display: hiddenDetail ? 'none' : 'inline-block',
  position: 'absolute',
  transform: `translate3d(0px, -100%, 0px)`,
}}

兩個(gè)Card組件的寬度和高度已經(jīng)設(shè)為一致,為了方便調(diào)試,將懸浮框的背景設(shè)為粉色;

使用絕對(duì)定位,讓其能夠覆蓋原始信息;

通過(guò)transform改變懸浮框的位置,不設(shè)置的話,懸浮框默認(rèn)被擠到下方,-100%表示在y軸上向上移動(dòng)懸浮框高度對(duì)應(yīng)的像素,由于兩個(gè)Card組件高度相同,因此可以覆蓋原始信息。

事件設(shè)置

第一個(gè)Card,即默認(rèn)顯示的元素,添加鼠標(biāo)移入事件:

onMouseEnter={(e) => {
  setHiddenDetail(!hiddenDetail)
}}

第二個(gè)Card,即懸浮框,添加鼠標(biāo)移出事件:

onMouseLeave={(e) => {
  // bug 向下移出不會(huì)觸發(fā) 
  // 因?yàn)橐迫肓说讓覥ard,執(zhí)行了setHiddenDetail(false)
  // 將移入事件改為 setHiddenDetail(!hiddenDetail)
  setHiddenDetail(true)
}}

這里我們使用!hiddenDetail,而不是直接設(shè)為true,

因?yàn)槿绻讓?code>DIV大于懸浮框的框的話,在懸浮框顯示的情況下,如果移出過(guò)程進(jìn)入了底層DIV,會(huì)導(dǎo)致懸浮框不會(huì)消失(雖然移出過(guò)程觸發(fā)了onMouseLeave,將狀態(tài)設(shè)為false,但移入底層DIV后,再次觸發(fā)onMouseEnter,將狀態(tài)設(shè)為true),這主要是應(yīng)對(duì)懸浮框沒(méi)有完全覆蓋底層元素的情況。

事件優(yōu)化

延遲顯示懸浮框

在底層元素的事件響應(yīng)中:

onMouseEnter={(e) => {  setHiddenDetail(!hiddenDetail)}}

將狀態(tài)改變?nèi)蝿?wù)用Timeout包裹,設(shè)定延時(shí)t,如果在移出該元素時(shí),定時(shí)器還沒(méi)有結(jié)束,則結(jié)束該定時(shí)器:

let loadDetailJob: NodeJS.Timeout | null = null
<Card
    bordered={false}
    bodyStyle={{ padding: 4 }}
    className={styles.video}
    cover={<img alt={video.title} src={video.poster} />}
    onMouseEnter={(e) => {
        loadDetailJob = setTimeout(() => {
            setHiddenDetail(!hiddenDetail)
        }, 500)
    }}
    onMouseLeave={(e) => {
        if (loadDetailJob) {
            clearTimeout(loadDetailJob)
        }
    }}
>
    <div className={styles.title}>
        {video.title}
    </div>
</Card>

懸浮框內(nèi)容懶加載

在騰訊視頻中,懸浮框顯示一小段視頻,但是一個(gè)頁(yè)面中包含多個(gè)懸浮框,如果一次全部加載這些資源,會(huì)造成比較大的流量浪費(fèi),因此,最后是要顯示懸浮框時(shí),才加載詳細(xì)內(nèi)容。

在本示例中,我們懸浮框顯示的圖片設(shè)為懶加載模式,我們需要增加一個(gè)狀態(tài)firstLoad記錄是否是第一次顯示懸浮框,如果是第一次,則設(shè)一個(gè)定時(shí)器模擬發(fā)送請(qǐng)求,獲取詳細(xì)內(nèi)容的鏈接。另一種情況是,在知道鏈接地址的情況下,不發(fā)送請(qǐng)求,將元素的src指向更高為正確的就行。

為了方便操作DOM元素,我們創(chuàng)建一個(gè)懸浮框的ref對(duì)象:detailRef。

const [firstLoad, setFirstLoad] = useState(true)
const detailRef = useRef<HTMLDivElement | null>(null)
useEffect(() => {
    // 第一次加載懸浮框,并且懸浮框狀態(tài)為顯示
    if (firstLoad && !hiddenDetail) {
    // 在知道路徑的情況下,可以直接修改路徑,Promise用于模擬向服務(wù)器發(fā)送請(qǐng)求的等待過(guò)程
        new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('load success')
            }, 1000)
        }).then(() => {
            setFirstLoad(false)
            detailRef.current!.querySelector('img')!.src = 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png'
        })
    }
}, [hiddenDetail])

完整代碼

import { Card } from 'antd';
import { Content } from 'antd/lib/layout/layout';
import { useEffect, useRef, useState } from 'react';
import { Image } from 'antd'
import { NavLink, useNavigate } from 'react-router-dom'
import styles from './css/VideoItem.module.css'
interface VideoItemProps {
    video: Video,
    topCategory?: string,
    subCategory?: string,
}
function getElementAbsPos(e: HTMLElement) {
    var t = e!.offsetTop;
    var l = e!.offsetLeft;
    while (e = e!.offsetParent as HTMLElement) {
        t += e.offsetTop;
        l += e.offsetLeft;
    }
    return { left: l, top: t };
}
export default function VideoItem(props: VideoItemProps) {
    const { video, topCategory, subCategory } = props
    const navigate = useNavigate()
    const [hiddenDetail, setHiddenDetail] = useState(true)
    const [firstLoad, setFirstLoad] = useState(true)
    const detailRef = useRef<HTMLDivElement | null>(null)
    let loadDetailJob: NodeJS.Timeout | null = null
    const to = (() => {
        let itemTop = Object.getOwnPropertyNames(video.category)[0]
        let itemSub = video.category[itemTop].length ? video.category[itemTop][0] : ''
        if (topCategory) {
            itemTop = topCategory
            itemSub = ''
            if (subCategory) {
                itemSub = subCategory
            }
        }
        if (itemSub) {
            return `/detail/${itemTop}/${itemSub}/${video.id}`
        } else {
            return `/detail/${itemTop}/${video.id}`
        }
    })()
    useEffect(() => {
        if (firstLoad && !hiddenDetail) {
            new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve('load success')
                }, 1000)
            }).then(() => {
                setFirstLoad(false)
                detailRef.current!.querySelector('img')!.src = 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png'
            })
        }
    }, [hiddenDetail])
    return (
        <NavLink to={to}>
            <Card
                bordered={false}
                bodyStyle={{ padding: 4 }}
                className={styles.video}
                cover={<img alt={video.title} src={video.poster} />}
                onMouseEnter={(e) => {
                    loadDetailJob = setTimeout(() => {
                        setHiddenDetail(!hiddenDetail)
                    }, 500)
                }}
                onMouseLeave={(e) => {
                    if (loadDetailJob) {
                        clearTimeout(loadDetailJob)
                    }
                }}
            >
                <div className={styles.title}>
                    {video.title}
                </div>
            </Card>
            <Card hoverable
                bordered={false}
                loading={firstLoad}
                ref={(c) => { detailRef.current = c }}
                style={{
                    backgroundColor: 'pink',
                    display: hiddenDetail ? 'none' : 'inline-block',
                    position: 'absolute',
                    transform: `translate3d(0px, -100%, 0px)`,
                }}
                bodyStyle={{ padding: 4 }}
                className={styles.video}
                cover={<img alt={video.title} src={'占位圖片鏈接'} />}
                onMouseLeave={(e) => {
                    // bug 向下移出不會(huì)觸發(fā) 
                    // 因?yàn)橐迫肓说讓覥ard,執(zhí)行了setHiddenDetail(false)
                    // 將移入事件改為 setHiddenDetail(!hiddenDetail)
                    setHiddenDetail(true)
                }}
            >
                <div className={styles.title}>
                    {video.title}
                </div>
            </Card>
        </NavLink>
    )
}

以上就是React 懸浮框內(nèi)容懶加載實(shí)例詳解的詳細(xì)內(nèi)容,更多關(guān)于React 懸浮框內(nèi)容懶加載的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • React開(kāi)發(fā)進(jìn)階redux saga使用原理詳解

    React開(kāi)發(fā)進(jìn)階redux saga使用原理詳解

    這篇文章主要為大家介紹了React開(kāi)發(fā)進(jìn)階redux saga使用原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • react native 仿微信聊天室實(shí)例代碼

    react native 仿微信聊天室實(shí)例代碼

    這篇文章主要介紹了react native 仿微信聊天室實(shí)例代碼,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-09-09
  • React虛擬渲染實(shí)現(xiàn)50個(gè)或者一百個(gè)圖表渲染

    React虛擬渲染實(shí)現(xiàn)50個(gè)或者一百個(gè)圖表渲染

    這篇文章主要為大家介紹了React虛擬渲染實(shí)現(xiàn)50個(gè)或者100個(gè)圖表渲染的實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • 一看就懂的ReactJs基礎(chǔ)入門教程-精華版

    一看就懂的ReactJs基礎(chǔ)入門教程-精華版

    現(xiàn)在最熱門的前端框架有AngularJS、React、Bootstrap等。自從接觸了ReactJS,ReactJs的虛擬DOM(Virtual DOM)和組件化的開(kāi)發(fā)深深的吸引了我,下面來(lái)跟我一起領(lǐng)略ReactJs的風(fēng)采吧~~ 文章有點(diǎn)長(zhǎng),耐心讀完,你會(huì)有很大收獲哦
    2021-04-04
  • 關(guān)于useEffect的第二個(gè)參數(shù)解讀

    關(guān)于useEffect的第二個(gè)參數(shù)解讀

    這篇文章主要介紹了關(guān)于useEffect的第二個(gè)參數(shù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • useEffect中不能使用async原理詳解

    useEffect中不能使用async原理詳解

    這篇文章主要為大家介紹了useEffect中為什么不能使用async的原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • 詳解React如何實(shí)現(xiàn)代碼分割Code Splitting

    詳解React如何實(shí)現(xiàn)代碼分割Code Splitting

    這篇文章主要為大家介紹了React如何實(shí)現(xiàn)代碼分割Code Splitting示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08
  • React國(guó)際化react-i18next詳解

    React國(guó)際化react-i18next詳解

    react-i18next 是基于 i18next 的一款強(qiáng)大的國(guó)際化框架,可以用于 react 和 react-native 應(yīng)用,是目前非常主流的國(guó)際化解決方案。這篇文章主要介紹了React國(guó)際化react-i18next的相關(guān)知識(shí),需要的朋友可以參考下
    2021-10-10
  • React?函數(shù)式組件和類式組件詳情

    React?函數(shù)式組件和類式組件詳情

    這篇文章主要介紹了React函數(shù)式組件和類式組件詳情,React是組件化的的JS庫(kù),組件化也是React的核心思想,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-08-08
  • 詳解React中錯(cuò)誤邊界的原理實(shí)現(xiàn)與應(yīng)用

    詳解React中錯(cuò)誤邊界的原理實(shí)現(xiàn)與應(yīng)用

    在React中,錯(cuò)誤邊界是一種特殊的組件,用于捕獲其子組件樹(shù)中發(fā)生的JavaScript錯(cuò)誤,并防止這些錯(cuò)誤冒泡至更高層,導(dǎo)致整個(gè)應(yīng)用崩潰,下面我們就來(lái)看看它的具體應(yīng)用吧
    2024-03-03

最新評(píng)論