react-native WebView獲取內(nèi)容高度

需求背景

我們APP里有個(gè)商品詳情頁(yè),頁(yè)面上半部分是自己寫(xiě)的界面,下半部分則要展示一段由后臺(tái)返回的html標(biāo)簽,圖文混排的形式。由于WebView如果不給定一個(gè)高度,將無(wú)法展示內(nèi)容,但html內(nèi)容是運(yùn)營(yíng)人員編寫(xiě)的,無(wú)法固定高度。我第一反應(yīng)是由于WebView沒(méi)有獲取內(nèi)容高度的api,所以有沒(méi)有好用的第三方組件呢?ps:最終方案可以直接看文章最后!

第三方組件

GitHub上一個(gè)高分組件,也是別人推薦的react-native-htmlview,其能渲染一段html標(biāo)簽的字符串而無(wú)需給定高度,官方例子:

import React from 'react';
import HTMLView from 'react-native-htmlview';

class App extends React.Component {
  render() {
    const htmlContent = `<p><a >&hearts; nice job!</a></p>`;

    return (
      <HTMLView
        value={htmlContent}
        stylesheet={styles}
      />
    );
  }
}

但我們很快發(fā)現(xiàn)在安卓里img渲染不出來(lái),在GitHub的issues里也有相應(yīng)的提問(wèn),解決辦法基本都是使用該組件的renderNode屬性,判斷是否為img標(biāo)簽,然后給一個(gè)固定高度

renderNode(node, index, siblings, parent, defaultRenderer) {
    if (node.name == 'img') {
      const { src, height } = node.attribs;
      const imageHeight = height || 300;
      return (
        <Image
          key={index}
          style={{ width: width * PixelRatio.get(), height: imageHeight * PixelRatio.get() }}
          source={{ uri: src }} />
      );
    }
  }

但我們的圖片怎么能固定高度呢,圖片不就變形了嗎?最終這個(gè)方案被放棄了。

后來(lái)我們找了另一個(gè)組件react-native-render-html,該組件使用簡(jiǎn)單點(diǎn),提供了很多屬性,對(duì)圖片的最大寬度能用屬性設(shè)置,在安卓上表現(xiàn)很好,圖片正常顯示。官方例子:

import React, { Component } from 'react';
import { ScrollView, Dimensions } from 'react-native';
import HTML from 'react-native-render-html';

const htmlContent = `
    <h1>This HTML snippet is now rendered with native components !</h1>
    <img src="https://i.imgur.com/dHLmxfO.jpg?2" />
`;

export default class Demo extends Component {
    render () {
        return (
            <ScrollView style={{ flex: 1 }}>
                <HTML html={htmlContent} imagesMaxWidth={Dimensions.get('window').width} />
            </ScrollView>
        );
    }
}

但……當(dāng)我們換上一張尺寸較大的圖片時(shí),ios端展示的圖片模糊了,無(wú)法忍受的那種。同樣GitHub的issues里也有相應(yīng)的提問(wèn)。有人給出了答案就是修改源碼,原因是圖片給了一個(gè)固定初始寬高,等圖片加載完后就變成stretched了,就模糊了;解決方法就是等圖片加載完后,設(shè)一個(gè)真實(shí)的寬高,這樣圖片就不模糊了。但這不是我心中完美的方法,并且后面發(fā)現(xiàn)其無(wú)法渲染span、em等標(biāo)簽,所以還是放棄了。

原生組件WebView

發(fā)現(xiàn)第三方組件都會(huì)有點(diǎn)問(wèn)題,正當(dāng)無(wú)奈的時(shí)候,腦子開(kāi)竅了。WebView沒(méi)有直接提供內(nèi)容高度的屬性,不代表沒(méi)有間接獲取內(nèi)容高度的屬性啊。百度一搜,各種答案,前面的那些折騰,簡(jiǎn)直愚蠢啊。

方法一

使用WebView的onNavigationStateChange屬性。獲取高度原理是當(dāng)文檔加載完后js獲取文檔高度然后添加到title標(biāo)簽中。這時(shí)通過(guò)監(jiān)聽(tīng)導(dǎo)航狀態(tài)變化的函數(shù) onNavigationStateChange 來(lái)將title的值讀取出來(lái)賦值給this.state.height從而使webview的高度做到自適應(yīng)。

constructor(props) {
    super(props);
    this.state={
      height:500,
    }
  }

<View style={{height:this.state.height}}>
    <WebView
        source={{html: `<!DOCTYPE html><html><body>${htmlContent}<script>window.onload=function(){window.location.hash = 1;document.title = document.body.clientHeight;}</script></body></html>`}}
        style={{flex:1}}
        bounces={false}
        scrollEnabled={false}
        automaticallyAdjustContentInsets={true}
        contentInset={{top:0,left:0}}
        onNavigationStateChange={(title)=>{
          if(title.title != undefined) {
            this.setState({
              height:(parseInt(title.title)+20)
            })
          }
        }}
        >
    </WebView>
</View>

但是如果我的source是一個(gè)uri呢,這種方法還是不夠靈活。

終極方法

使用WebView的injectedJavaScriptonMessage屬性。ps:在低版本的RN中無(wú)法使用onMessage屬性官方解釋?zhuān)?/p>

injectedJavaScript string

設(shè)置在網(wǎng)頁(yè)加載之前注入的一段JS代碼。
onMessage function

在webview內(nèi)部的網(wǎng)頁(yè)中調(diào)用`window.postMessage`方法時(shí)可以觸發(fā)此屬性對(duì)應(yīng)的函數(shù),從而實(shí)現(xiàn)網(wǎng)頁(yè)和RN之間的數(shù)據(jù)交換。 設(shè)置此屬性的同時(shí)會(huì)在webview中注入一個(gè)`postMessage`的全局函數(shù)并覆蓋可能已經(jīng)存在的同名實(shí)現(xiàn)。

網(wǎng)頁(yè)端的`window.postMessage`只發(fā)送一個(gè)參數(shù)`data`,此參數(shù)封裝在RN端的event對(duì)象中,即`event.nativeEvent.data`。`data`只能是一個(gè)字符串。

思路是使用injectedJavaScript注入一段js代碼獲取網(wǎng)頁(yè)內(nèi)容高度,然后調(diào)用window.postMessage方法把高度回調(diào)給onMessage方法,然后setState,改變webView高度,從而實(shí)現(xiàn)自適應(yīng)。直接上代碼:

import React, { Component } from 'react'
import {
  WebView,
  Dimensions,
  ScrollView
} from 'react-native'

const BaseScript =
    `
    (function () {
        var height = null;
        function changeHeight() {
          if (document.body.scrollHeight != height) {
            height = document.body.scrollHeight;
            if (window.postMessage) {
              window.postMessage(JSON.stringify({
                type: 'setHeight',
                height: height,
              }))
            }
          }
        }
        setTimeout(changeHeight, 300);
    } ())
    `

const HTMLTEXT = `<h1>This HTML snippet is now rendered with native components !</h1>
    <img src="https://i.imgur.com/dHLmxfO.jpg?2" />`

class AutoHeightWebView extends Component {
  constructor (props) {
    super(props);
    this.state = ({
      height: 0
    })
  }

  /**
   * web端發(fā)送過(guò)來(lái)的交互消息
   */
  onMessage (event) {
    try {
      const action = JSON.parse(event.nativeEvent.data)
      if (action.type === 'setHeight' && action.height > 0) {
        this.setState({ height: action.height })
      }
    } catch (error) {
      // pass
    }
  }

  render () {
    return (
      <ScrollView>
        <WebView
          injectedJavaScript={BaseScript}
          style={{
            width: Dimensions.get('window').width,
            height: this.state.height
          }}
          automaticallyAdjustContentInsets
          source={{ html: HTMLTEXT }}// 這里可以使用uri
          decelerationRate='normal'
          scalesPageToFit
          javaScriptEnabled // 僅限Android平臺(tái)。iOS平臺(tái)JavaScript是默認(rèn)開(kāi)啟的。
          domStorageEnabled // 適用于安卓
          scrollEnabled={false}
          onMessage={this.onMessage.bind(this)}
        />
      </ScrollView>
    )
  }
}

export default RZWebView

這里有點(diǎn)小插曲,我們?cè)?code>BaseScript這段js字符串中,使用//寫(xiě)了點(diǎn)注釋?zhuān)Y(jié)果安卓端onMessage方法就不被調(diào)用了。非常郁悶,最后查找資料發(fā)現(xiàn)這種//注釋方法是會(huì)導(dǎo)致這段js不被執(zhí)行的,正確的注釋方式是/**/

最后完美解決問(wèn)題,完成需求。這中間過(guò)程艱辛,希望本文的總結(jié)能幫到大家少走冤路。謝謝!

參考文章

《ReactNative WebView高度自適應(yīng)》
《React-Native WebView 測(cè)量網(wǎng)頁(yè)高度》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容