react-native WebView獲取內容高度

需求背景

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

第三方組件

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

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ā)現在安卓里img渲染不出來,在GitHub的issues里也有相應的提問,解決辦法基本都是使用該組件的renderNode屬性,判斷是否為img標簽,然后給一個固定高度

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 }} />
      );
    }
  }

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

后來我們找了另一個組件react-native-render-html,該組件使用簡單點,提供了很多屬性,對圖片的最大寬度能用屬性設置,在安卓上表現很好,圖片正常顯示。官方例子:

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>
        );
    }
}

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

原生組件WebView

發(fā)現第三方組件都會有點問題,正當無奈的時候,腦子開竅了。WebView沒有直接提供內容高度的屬性,不代表沒有間接獲取內容高度的屬性啊。百度一搜,各種答案,前面的那些折騰,簡直愚蠢啊。

方法一

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

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是一個uri呢,這種方法還是不夠靈活。

終極方法

使用WebView的injectedJavaScriptonMessage屬性。ps:在低版本的RN中無法使用onMessage屬性官方解釋:

injectedJavaScript string

設置在網頁加載之前注入的一段JS代碼。
onMessage function

在webview內部的網頁中調用`window.postMessage`方法時可以觸發(fā)此屬性對應的函數,從而實現網頁和RN之間的數據交換。 設置此屬性的同時會在webview中注入一個`postMessage`的全局函數并覆蓋可能已經存在的同名實現。

網頁端的`window.postMessage`只發(fā)送一個參數`data`,此參數封裝在RN端的event對象中,即`event.nativeEvent.data`。`data`只能是一個字符串。

思路是使用injectedJavaScript注入一段js代碼獲取網頁內容高度,然后調用window.postMessage方法把高度回調給onMessage方法,然后setState,改變webView高度,從而實現自適應。直接上代碼:

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ā)送過來的交互消息
   */
  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平臺。iOS平臺JavaScript是默認開啟的。
          domStorageEnabled // 適用于安卓
          scrollEnabled={false}
          onMessage={this.onMessage.bind(this)}
        />
      </ScrollView>
    )
  }
}

export default RZWebView

這里有點小插曲,我們在BaseScript這段js字符串中,使用//寫了點注釋,結果安卓端onMessage方法就不被調用了。非常郁悶,最后查找資料發(fā)現這種//注釋方法是會導致這段js不被執(zhí)行的,正確的注釋方式是/**/。

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

參考文章

《ReactNative WebView高度自適應》
《React-Native WebView 測量網頁高度》

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容