需求背景: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}
/>