我们最近要做一个React Native
的产品详情页,里面有一部分内容展示的是服务端下发的html
标签(运营人员编写的,内容不固定,且很随意)。需求点是:1、展示富文本,包括img
、span
、div
等基本html
标签;2、控制富文本的整体样式,比如业务配置的图片大小不一,我们要统一已屏幕宽度为基准,高度自适应。
要实现这个需求,我第一想到的就是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,使用方法如下:
import HTML from 'react-native-render-html'
<HTML
html={wrapper}
tagsStyles
/>
这里又遇到了问题,ios
上展示的图片很模糊,而且也存在第一个组件的问题:要想全局控制整体样式,还是要根据每个标签做适配,开发成本有些大。
至此,我们要想用webView
,那就必须根据webView
内容控制高度。
import {
WebView
} from 'react-native';
<WebView
style={{
width: Dimensions.get('window').width,
height: this.state.height
}}
injectedJavaScript={injectedJs}
automaticallyAdjustContentInsets={true}
source={{html: ` ${this.state.value}`}}
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
在这个业务场景下是更适合我的。
这里简单记录一下,希望能给遇到这个问题的小伙伴一些帮助。