簡單的react-naitve圖片懶加載

類似知乎或者微信,在文章詳情頁面,圖片并非是一次性加載完成,而是在滑動到快看到圖片時才開始加載,很明顯原生 Image 組件并不支持這種特性,所以需要封裝一層。

原理

首先圖片組件必須放在 ScrollView 組件內。
圖片組件在頁面完成布局后,根據onLayout屬性可以獲取到這個組件在容器中的 x、y 值。
監聽 ScrollView 滾動事件,onScroll會給出內容偏移量 contentOffset。當內容偏移量大于等于圖片組件的 y 值減去屏幕高度時,就可以加載圖片了。

懶加載原理

具體實現

首先搭建好頁面,為了填充內容復制了知乎上的一篇文章《如何去閱讀并學習一些優秀的開源框架的源碼?》

// imageLazyloadDemo.js
import React, {Component} from 'react'
import {
    StyleSheet,
    View,
    Text,
    // Image,
    ScrollView,
} from 'react-native'
import Image from '../components/Image'
export default class ImageDemo extends Component {
    constructor(props) {
        super(props);
        this.state = {
            y: 0
        }
        this._onScroll = this._onScroll.bind(this)
    }
    // 滑動觸發
    _onScroll(e) {
        // 獲取滑動的距離
        let {y} = e.nativeEvent.contentOffset;
        this.setState({
            y
        })
    }
    render() {
        return (
            <ScrollView 
                style = {styles.container}
                onScroll = {this._onScroll}
            >
                <Text style = {[styles.text, styles.title]}>假設這是文章詳情頁,存在很多文字和圖片</Text>
                <Text style = {styles.text}>在我閱讀的前端庫、Python后臺庫的過程中,我們都是以造輪子為目的展開的。所以在最開始的時候,我需要一個可以工作,并且擁有我想要的功能的版本。</Text>
                <Text style = {styles.text}>緊接著,我就可以開始去實踐這個版本中的一些功能,并理解他們是怎么工作的。再用git大法展開之前修改的內容,可以使用IDE自帶的Diff工具:</Text>
                <Image
                    source = {{uri: 'https://pic4.zhimg.com/5bc23b15e827a033d2b4966b6038d987_b.jpg'}}
                    y = {this.state.y}
                />
                <Text style = {styles.text}>或者類似于SourceTree這樣的工具,來查看修改的內容。</Text>
                <Text style = {styles.text}>在我們理解了基本的核心功能后,我們就可以向后查看大、中版本的更新內容了。</Text>
                <Text style = {styles.text}>開始之前,我們希望大家對版本號管理有一些基本的認識。</Text>
                <Text style = {styles.text}>我最早閱讀的開始軟件是Linux,而下面則是Linux的Release過程:</Text>
                <Image
                    source = {{uri: 'https://pic3.zhimg.com/f9d7c5343f3da040f891149a8993ed7e_b.png'}}
                    y = {this.state.y}
                />
                <Text style = {styles.text}>表格源自一本書叫《Linux內核0.11(0.95)完全注釋》,簡單地再介紹一下:</Text>
                <Text style = {styles.text}>版本0.00是一個hello,world程序</Text>
                <Text style = {styles.text}>版本0.01包含了可以工作的代碼</Text>
                <Text style = {styles.text}>版本0.11是基本可以正常的版本</Text>
                <Text style = {styles.text}>1.項目初版本時,版本號可以為 0.1 或 0.1.0, 也可以為 1.0 或 1.0.0,如果你為人很低調,我想你會選擇那個主版本號為 0 的方式;</Text>
                <Image
                    source = {{uri: 'https://pic4.zhimg.com/6a32830fab3b9105fecef3d4f830afe7_b.png'}}
                    y = {this.state.y}
                />
            </ScrollView>
        )
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#F5FCFF',
        paddingHorizontal: 10
    },
    text: {
        fontSize: 16,
        marginVertical: 10
    },
    title: {
        fontSize: 20,
        marginTop: 10
    }
})

重點在_onScroll函數上,獲取到每次滑動的內容偏移量 contentOffset.y,并通過 state 傳遞給 Image 組件。

<Image
    source = {{uri: 'https://pic4.zhimg.com/5bc23b15e827a033d2b4966b6038d987_b.jpg'}}
    y = {this.state.y}
/>

這樣 Image 組件就能夠知道自己是否出現在可視范圍內了。

這里 Image 組件是一個封裝后的組件:

// components/Image.js
import React, {Component} from 'react'
import {
    StyleSheet,
    View,
    Image,
    Text,
    Dimensions,
    InteractionManager
} from 'react-native'
// 獲取屏幕寬高
const screenWidth = Dimensions.get('window').width
const screenHeight = Dimensions.get('window').height
export default class CustomImage extends Component {
    constructor(props) {
        super(props)
        // 先給圖片一個默認寬高
        this.state = {
            width: screenWidth,
            height: 300,
            loaded: false
        }
        this._onLayout = this._onLayout.bind(this)
    }
    // 2、獲取組件初始化位置
    _onLayout(e, node) {
        let {y} = e.nativeEvent.layout
        alert(y)
        this.setState({
            offsetY: y
        })
    }   
    // 獲取加載中、加載失敗視圖的函數
    _renderLoad(text) {
        return(
            <View 
                style = {styles.loadContainer}
                onLayout = {this._onLayout}
            >
                <Text 
                    style = {styles.loadText}
                >{text}</Text>
            </View>
        )
    }
    render() {
        const {source} = this.props
        if(this.state.loaded) {
            // 如果真正的圖片加載好
            return <Image 
                source = {source}
                style = {{height: this.state.height}}
                resizeMode = {'contain'}
            />
        }
        // 1、會先顯示加載中視圖
        return this._renderLoad('正在加載中...')
    }
}
const styles = StyleSheet.create({
    // 加載容器樣式
    loadContainer: {
        backgroundColor: '#eee',
        justifyContent: 'center',
        alignItems: 'center',
        height: 100,
        marginVertical: 10
    },
    // 加載樣式
    loadText: {
        fontSize: 20,
        color: '#ccc'
    }
)

1、先加載 loading 視圖。
2、獲取到該 loading 視圖在 ScrollView 容器內的位置,有用的是 loading 距內容頂部的距離 y。

OK,然后可以開始計算了,如果傳過來的 y + 屏幕高度 >= loading 視圖距內容頂部的距離 y,就可以獲取遠程圖片了。

// components/Image.js
//...
    // 從網絡請求圖片的方法
    _fetchImg() {
        InteractionManager.runAfterInteractions(() => {
            const {source, style} = this.props
            if(source.uri) {
                // 如果是網絡圖片 在頁面還未加載前,獲取圖片寬高
                Image.getSize(source.uri, (w, h) => {
                    let imgHeight = (h/w)*this.state.width
                    this.setState({
                        height: imgHeight,
                        loaded: true
                    })
                }, (err) => {
                    // 獲取圖片寬高或者下載圖片失敗
                    this.setState({
                        loadFail: true
                    })
                })
            }
        })
    }

    render() {
        const {source, y} = this.props
        // 3、判斷是否可以加載圖片了
        if(y + screenHeight >= this.state.offsetY && !this.state.loaded) {
            // 請求圖片
            this._fetchImg()
        }
        // 4、如果加載了遠程圖片,就會渲染這里
        if(this.state.loaded) {
            // 如果真正的圖片加載好
            return <Image 
                source = {source}
                style = {{height: this.state.height}}
                resizeMode = {'contain'}
            />
        }
        // 1、會先顯示加載中視圖
        return this._renderLoad('正在加載中...')
    }
lazyImg.gif

看起來還不錯。。。

總結

至此,完成了一個最最最簡單的圖片懶加載組件。
當然還可以繼續做一些優化,比如當網絡不好時,加載圖片顯示加載失敗,點擊可以重新加載等等。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容