需求背景:react-native的ScrollView组件内嵌入WebView,纵向的FlatList切换item时切换WebView的url。
难点:解决WebView和外部ScrollView的滑动冲突
<ScrollView>
<FlatList />
<WebView />
</ScrollView>
方案一: WebView的高度、宽度固定,手势落在WebView内时滑动时阻止外部ScrollView的滑动事件,手势离开时恢复ScrollView的滑动事件。
<ScrollView scrollEnabled={this.state.scrollEnabled}>
<WebView
source={{uri: currentUrl}}
style={styles.webview}
scalesPageToFit={false}
onLoad={() => {this.setSafeState({load: true})}}
onTouchStart={() => {this.setSafeState({scrollEnabled: false})}}
onTouchMove={() => {this.setSafeState({scrollEnabled: false})}}
onTouchEnd={() => {this.setSafeState({scrollEnabled: true})}}
/>
...
</ScrollView>
问题:由于频繁的滑动手势,setState的异步效率过慢导致scrollEnabled
并没有随着手势改变而立马改变,导致在WebView内部上下滑动时,整个页面(ScrollView)也会跟着滑动。
解决方案:react-native的直接操作setNativeProps
API
setScrollEnabled = (bool) => {
this._WebViewCom.setNativeProps({scrollEnabled: bool})
}
<ScrollView scrollEnabled={this.state.scrollEnabled}>
<WebView
source={{uri: currentUrl}}
style={styles.webview}
scalesPageToFit={false}
bounces={false}//webview 内容到达底部时是否进行回弹
ref={component => {this._WebViewCom = component}}
onLoad={() => {this.setSafeState({load: true})}}
onTouchStart={this.setScrollEnabled.bind(this, false)}
onTouchMove={this.setScrollEnabled.bind(this, false)}
onTouchEnd={this.setScrollEnabled.bind(this, true)}
/>
...
</ScrollView>
方案二:设置WebView的高度和表格的高度一样,这样也可以完美避开ScrollView和WebView的滑动冲突,WebView内表格还可以左右滑动。
1、使用postMessage实现native和webView的通信,webView告诉native表格的高度,naitve再设置webView的高度。
// postMessage接收表格的高度,实现表格高度自适应
onMessage = (e) => {
const event = e.nativeEvent;
const height = parseInt(event.data);
this.setSafeState({height});
}
<WebView
onMessage={this.onMessage}
source={{uri: currentUrl}}
style={[styles.webview, height > 0 && {height}]}
onLoad={() => {this.setSafeState({load: true})}}
javaScriptEnabled={true}
onError={this.onError}
injectedJavaScript={`
// 页面自适用
let metaTag=document.createElement('meta');
metaTag.name = "viewport"
metaTag.content = "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
document.getElementsByTagName('head')[0].appendChild(metaTag);
// 获取表格的高度
let height = window.getComputedStyle(document.getElementsByTagName('table')[0]).height;
let h = parseInt(height) + 44;
window.ReactNativeWebView.postMessage(h);
`}
/>
问题: WebView页面是load完成之后从执行injectedJavaScript的js代码,所以WebView是有一个固定的高度,等接收到postMessage传过来的height之后,将WebView的高度设成这个height。一开始比较快,但是切换WebView的url时,如果表格比较大会很慢。
解决方案:让后端在返回的url的h5的<head>
里加上页面自适应的代码:
<meta name="viewport" content="height=device-height, initial-scale=1, maximum-scale=1.0, user-scalable=0" />
并且页面onload完之后返回h5表格的高度, (也是加在h5的<hea>
下)
<script>
window.onload=function(){
let height = window.getComputedStyle(document.getElementsByTagName('table')[0]).height;
let h = parseInt(height, 10);
document.title = h;
}
</script>
react-native这边使用onNavigationStateChange(当导航状态发生变化的时候调用),页面不再需要之前使用postMessage那样等页面onload完之后执行js才能拿到height。
// 接收表格的高度,实现表格高度自适,title的值为table的高度
onNavigationStateChange = (navState) => {
if (navState.title) {
const height = parseInt(navState.title, 10) || 0; // turn NaN to 0
this.setSafeState({height: height})
}
}
<WebView
source={{uri: currentUrl}}
style={[styles.webview, height > 0 && {height}]}
onLoad={() => {this.setSafeState({load: true})}}
onError={this.onError}
bounces={false}
onNavigationStateChange={this.onNavigationStateChange}
/>