React Native展示富文本(HTML標籤)最佳解決方案

需求背景

我們最近要做一個React Native的產品詳情頁,裏面有一部分內容展示的是服務端下發的html標籤(運營人員編寫的,內容不固定,且很隨意)。需求點是:1、展示富文本,包括imgspandiv等基本html標籤;2、控制富文本的整體樣式,比如業務配置的圖片大小不一,我們要統一已屏幕寬度爲基準,高度自適應。

第三方組件react-native-htmlview

要實現這個需求,我第一想到的就是react-native-htmlview,我們之前斷斷續續的RN頁面裏的富文本展示需求用的都是這個組件。而且這個組件有一個好處就是不用傳入高度,能根據富文本內容自適應高度。使用也很簡單:

import HTMLView from 'react-native-htmlview'
<HTMLView 
	value={wrapper}
/>

只需要把富文本傳給value就行了,可是實際展示效果令人堪憂。
在這裏插入圖片描述
文本雖然展示比較差,但圖片展示還不錯。
在這裏插入圖片描述
於是我就着手去支持文本標籤,最終效果如圖:
在這裏插入圖片描述
還不錯吧。如果你的需求就只是展示下富文本(圖片、文本),那這個開源組件就可以滿足你的需求了。先說下我是如何把文本樣式正確展示出來的:

<HTMLView 
	value={wrapper}
	renderNode={renderNode}
/>

function renderNode(node, index, siblings, parent, defaultRenderer) {
			if(node.name == 'div') {
				const View1 = styled.View`
				${ node.attribs.style };
			  `;
		  
			  return (
				  <View1 key={index}>
					{defaultRenderer(node.children, parent)}
				  </View1>
			  );
			} else if (node.name == 'p' || node.name == 'span') {
		  
			  const Text1 = styled.Text`
				${ node.attribs.style };
			  `;
		  
			  return (
				  <Text1 key={index}>
					{defaultRenderer(node.children, parent)}
				  </Text1>
			  );
			}else if(node.name == 'strong'){
			  const Text2 = styled.Text`
				${ node.attribs.style };
				font-weight:bold;
			  `;
		  
			  return (
				  <Text2 key={index}>
					{defaultRenderer(node.children, parent)}
				  </Text2>
			  );
			}
}

renderNode這個接口提供了讓我們自定義解析html標籤的方法,這裏我用到了styled-components,它能去讀取html標籤的css樣式,然後轉換成RN支持的樣式代碼,如上述的Text1Text2
好,現在能展示文本和圖片了,然而我們的需求不止這麼簡單,我們還要統一控制樣式:圖片不能有大有小,必須自適應屏幕寬度。
這裏,我對img標籤做了處理,如果是img標籤,輸出的是可以根據寬度,按照比例拿到高度的圖片。

if(node.name == 'img'){
				return <AutoSizedImage
					key={index}
					source={{uri: node.attribs.src}}
					style={{width:330, height:0}}
				/>
}

然而,ios上可以了,android上輸出的圖片只能是默認大小,即使我已經重新給高度賦值了。

import React, { PureComponent, Component } from 'react';
import {
  Image,
  View,
} from 'react-native';

const baseStyle = {
  backgroundColor: '#0f0',
};

export default class AutoSizedImage extends Component {
  constructor(props) {
    super(props);
    this.state = {
      width: this.props.style.width || 1,
      height: this.props.style.height || 1,
    };
  }

  componentDidMount() {
    Image.getSize(this.props.source.uri, (w, h) => {
      console.log('setState')
      this.setState({ width: w, height: h });
    });
  }

  render() {
    const finalSize = {};

    finalSize.width = this.props.style.width;
    const ratio = this.props.style.width / this.state.width;
    finalSize.height = Math.floor(this.state.height * ratio);

    console.log('height='+finalSize.height)
    return <Image style={{width: finalSize.width, height: finalSize.height}} source={this.props.source} />
  }
}

而且,我們要控制的樣式有很多,如果每個都自定義一個組件,可維護性太差,故暫時放棄。

第三方組件react-native-render-html

接下來,我用到了網上很火的react-native-render-html,使用方法如下:

import HTML from 'react-native-render-html'
<HTML
	html={wrapper}
	tagsStyles
/>

這裏又遇到了問題,ios上展示的圖片很模糊,而且也存在第一個組件的問題:要想全局控制整體樣式,還是要根據每個標籤做適配,開發成本有些大。

原生組件WebView

至此,我們要想用webView,那就必須根據webView內容控制高度。

import {
	WebView
} from 'react-native';

<WebView
						style={{
              				width: Dimensions.get('window').width,
              				height: this.state.height
						}}
						injectedJavaScript={injectedJs}
						automaticallyAdjustContentInsets={true}
						source={{html: `<!DOCTYPE html><html> <style type="text/css">
						.tour_product_explain img{ display: block!important; vertical-align: top!important; width: 100%!important;}
						.tour_product_explain{ padding: 0 15px 20px 15px;}
						.tour_product_explain *{text-align: left!important;
							font-size: 14px!important;
							line-height: 1.3!important;
							font-family: Arial,"Lucida Grande",Verdana,"Microsoft YaHei",hei!important;
							float: none!important;
							padding: 0!important;
							position: static!important;
							height: auto!important}
						</style><body><div class='tour_product_explain' id='content'>${this.state.value}</div></body></html>`}}
						scalesPageToFit={true}
						javaScriptEnabled={true} // 僅限Android平臺。iOS平臺JavaScript是默認開啓的。
						domStorageEnabled={true} // 適用於安卓a
						scrollEnabled={false}
						onMessage={(event)=>{
							 console.log(event.nativeEvent.data )
                			this.setState({height: +event.nativeEvent.data})
						}}
/>

const injectedJs = 'setInterval(() => {window.parent.postMessage(document.getElementById("content").clientHeight)}, 500)'

這裏,富文本可以完全按照我們的需求展示,但是,webview的高度太大了,拿到的offsetHeight比實際高度大很多。
在這裏插入圖片描述
這個問題困擾了我整整兩天,偶然一次調試中,高度居然正確了。這次調試我做了什麼呢?我只是把webview的初始高度由100改成了0啊!
在這裏插入圖片描述
webview高度居然就算對了!
在這裏插入圖片描述
爲什麼初始高度會影響offsetHeight的值,我這裏還不清楚,瞭解的小夥伴可以給我留意。


2019.6.24
webview的方案在後來的開發過程中又被我放棄了,因爲webview加載富文本太慢了。我這邊測試下來,是要等所有圖片加載完成才能給出回調,這樣就要等的時間太長了。
最終我還是決定用react-native-render-html,而它確實不能很好地滿足我們的需求,目前我的做法是把不滿足我們需求的功能,對源碼做了些修改。

最後

至此,react native裏展示富文本就徹底解決了。原理是給富文本注入一段JS代碼,讓其可以把父div的高度回調給我,我再去setState設置webview高度。
至於上面介紹的兩個開源組件,這兩天我也去讀了裏面的實現:它們其實是把html標籤對應渲染成RN中的Text或者View,這其實是讓我沒法理解的:要先去解析html標籤,再展示成原生組件,那爲什麼不直接讓webView直接去解析呢?
如果你的業務場景只是要正確展示出富文本,那這兩個開源組件還是推薦的,只是我這裏要全局統一控制樣式,webView在這個業務場景下是更適合我的。

這裏簡單記錄一下,希望能給遇到這個問題的小夥伴一些幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章