最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

界面隱藏

一個容器放置視頻,默認情況下

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

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

懶加載

React監(jiān)聽鼠標移入(獲取坐標)

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

注意事件類型React.MouseEvent。

合成事件 – React (reactjs.org)

獲取Element的絕對位置

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

ts中:

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

mdn中: querySelectorgetElementById兩者均返回Element。

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

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

實現(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實現(xiàn)

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

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

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

原始代碼

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)點擊事件,跳轉(zhuǎn)到視頻詳情頁,以上代碼還不含與本文相關(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)表示是否隱藏懸浮框:

默認隱藏

const [hiddenDetail, setHiddenDetail] = useState(true)

樣式設(shè)置

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

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

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

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

事件設(shè)置

第一個Card,即默認顯示的元素,添加鼠標移入事件:

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

第二個Card,即懸浮框,添加鼠標移出事件:

onMouseLeave={(e) => {
  // bug 向下移出不會觸發(fā) 
  // 因為移入了底層Card,執(zhí)行了setHiddenDetail(false)
  // 將移入事件改為 setHiddenDetail(!hiddenDetail)
  setHiddenDetail(true)
}}

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

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

事件優(yōu)化

延遲顯示懸浮框

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

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

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

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)容懶加載

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

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

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

const [firstLoad, setFirstLoad] = useState(true)
const detailRef = useRef<HTMLDivElement | null>(null)
useEffect(() => {
    // 第一次加載懸浮框,并且懸浮框狀態(tài)為顯示
    if (firstLoad && !hiddenDetail) {
    // 在知道路徑的情況下,可以直接修改路徑,Promise用于模擬向服務(wù)器發(fā)送請求的等待過程
        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 向下移出不會觸發(fā) 
                    // 因為移入了底層Card,執(zhí)行了setHiddenDetail(false)
                    // 將移入事件改為 setHiddenDetail(!hiddenDetail)
                    setHiddenDetail(true)
                }}
            >
                <div className={styles.title}>
                    {video.title}
                </div>
            </Card>
        </NavLink>
    )
}

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

相關(guān)文章

  • vue3中獲取ref元素的幾種方式總結(jié)

    vue3中獲取ref元素的幾種方式總結(jié)

    這篇文章主要介紹了vue3中獲取ref元素的幾種方式總結(jié),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • React Umi國際化配置方法

    React Umi國際化配置方法

    這篇文章主要介紹了React Umi國際化配置方法,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2024-04-04
  • react中setState的執(zhí)行機制詳解

    react中setState的執(zhí)行機制詳解

    setState() 的執(zhí)行機制包括狀態(tài)合并、批量更新、異步更新、虛擬 DOM 比較和渲染組件等步驟,這樣可以提高性能并優(yōu)化渲染過程,這篇文章主要介紹了react中的setState的執(zhí)行機制,需要的朋友可以參考下
    2023-10-10
  • 解決React報錯Cannot?find?namespace?context

    解決React報錯Cannot?find?namespace?context

    這篇文章主要為大家介紹了React報錯Cannot?find?namespace?context分析解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • 詳解React?hooks組件通信方法

    詳解React?hooks組件通信方法

    這篇文章主要介紹了React?hooks組件通信,在開發(fā)中組件通信是React中的一個重要的知識點,本文通過實例代碼給大家講解react hooks中常用的父子、跨組件通信的方法,需要的朋友可以參考下
    2022-07-07
  • 使用useImperativeHandle時父組件第一次沒拿到子組件的問題

    使用useImperativeHandle時父組件第一次沒拿到子組件的問題

    這篇文章主要介紹了使用useImperativeHandle時父組件第一次沒拿到子組件的問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • React無限滾動加載列表組件的封裝實現(xiàn)

    React無限滾動加載列表組件的封裝實現(xiàn)

    無限下拉加載技術(shù)是用戶在大量成塊的內(nèi)容面前一直滾動查看,本文主要介紹了React無限滾動加載列表組件的封裝實現(xiàn),具有一定的參考價值,感興趣的可以了解一下
    2023-12-12
  • React實現(xiàn)雙向綁定示例代碼

    React實現(xiàn)雙向綁定示例代碼

    這篇文章給大家介紹了在React中如何實現(xiàn)雙向綁定,文中給出了示例代碼,對大家的理解與學(xué)習(xí)很有幫助,有需要的朋友下面來一起看看吧。
    2016-09-09
  • React Hooks使用常見的坑

    React Hooks使用常見的坑

    React Hooks 是 React 16.8 引入的新特性,允許我們在不使用 Class 的前提下使用 state 和其他特性。接下來通過本文給大家分享React Hooks使用避坑指南,一起學(xué)習(xí)下吧
    2021-06-06
  • React使用ref進行訪問DOM元素或組件

    React使用ref進行訪問DOM元素或組件

    在 React 里,ref 就像是一個神奇的小助手,能讓你直接去訪問 DOM 元素或者組件實例,下面就跟隨小編一起來學(xué)習(xí)一下具體的使用方法吧
    2025-03-03

最新評論

荥经县| 乌兰浩特市| 新津县| 罗源县| 芦溪县| 闵行区| 敦煌市| 鹿邑县| 南江县| 松江区| 唐河县| 龙井市| 辛集市| 札达县| 海兴县| 金溪县| 宜兴市| 荣昌县| 浦东新区| 成武县| 白水县| 普兰店市| 蒙自县| 浑源县| 兴和县| 威远县| 礼泉县| 武清区| 慈利县| 扎赉特旗| 宝兴县| 仁布县| 大邑县| 巫溪县| 成安县| 鞍山市| 攀枝花市| 鄄城县| 白银市| 封丘县| 岳普湖县|