需求背景
我們最近要做一個React Native
的產品詳情頁,裏面有一部分內容展示的是服務端下發的html
標籤(運營人員編寫的,內容不固定,且很隨意)。需求點是:1、展示富文本,包括img
、span
、div
等基本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
支持的樣式代碼,如上述的Text1
和Text2
。
好,現在能展示文本和圖片了,然而我們的需求不止這麼簡單,我們還要統一控制樣式:圖片不能有大有小,必須自適應屏幕寬度。
這裏,我對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
在這個業務場景下是更適合我的。
這裏簡單記錄一下,希望能給遇到這個問題的小夥伴一些幫助。