React Native Android端WebView onLoadEnd事件加载成功后不触发问题
I get it, dealing with WebView load events on Android can be super frustrating—especially when your custom full-screen loader disappears before the page (and its background image) is actually ready. Let’s break down why this happens and how to fix it for good:
Why Does This Happen on Android?
Android’s WebView implementation has a quirk where it fires onLoadEnd earlier than expected. Often, this happens when the initial DOM is parsed but before all critical resources (like your background image, stylesheets, or payment scripts) finish loading. This is different from iOS, where the event waits for the entire page to be fully rendered.
Solution 1: Use the Web Page’s window.onload to Signal Readiness (Most Reliable)
The best way to ensure you only hide the loader when the page is truly ready is to have the web page itself send a message to your React Native app once all resources are loaded. Here’s how to implement this:
- Inject a small script into the WebView that listens for the web page’s
window.onloadevent (which fires when all images, styles, and scripts are fully loaded). - Listen for that message in your React Native component and hide the loader when it’s received.
import React, { Component } from 'react'; import { View, WebView, StyleSheet, Image } from 'react-native'; class PaymentWebView extends Component { state = { isLoading: true, }; handlePageReady = (event) => { if (event.nativeEvent.data === 'pageLoaded') { this.setState({ isLoading: false }); } }; render() { const { isLoading } = this.state; const paymentUrl = 'your-payment-page-url'; return ( <View style={styles.container}> {/* Your custom full-screen loader with background image */} {isLoading && ( <View style={styles.loaderWrapper}> <Image source={require('./path-to-your-background-image.png')} style={styles.loaderBackground} /> {/* Add your spinner or loading text here */} </View> )} <WebView source={{ uri: paymentUrl }} style={styles.webView} javaScriptEnabled={true} injectedJavaScript={` // Wait for the web page to fully load all resources window.onload = function() { // Send a message to React Native to hide the loader window.ReactNativeWebView.postMessage('pageLoaded'); }; true; // Required for injectedJavaScript to execute properly `} onMessage={this.handlePageReady} // Keep your existing WebView props here /> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, }, loaderWrapper: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center', }, loaderBackground: { position: 'absolute', width: '100%', height: '100%', resizeMode: 'cover', }, webView: { flex: 1, }, }); export default PaymentWebView;
Edge Case Adjustment:
If your payment page loads dynamic content after window.onload (like AJAX-powered payment forms), modify the injected script to wait for specific elements instead:
injectedJavaScript={` function waitForPaymentForm() { if (document.getElementById('payment-submit-form')) { window.ReactNativeWebView.postMessage('pageLoaded'); } else { setTimeout(waitForPaymentForm, 100); } } waitForPaymentForm(); true; `}
Solution 2: Combine onProgress and onLoad (Fallback for Third-Party Pages)
If you can’t modify the web page’s code (e.g., it’s a third-party payment gateway), use the WebView’s progress tracking to ensure full load completion:
state = { isLoading: true, loadProgress: 0, domLoaded: false, }; handleDomLoad = () => { this.setState({ domLoaded: true }); this.checkIfReady(); }; handleProgressUpdate = (event) => { this.setState({ loadProgress: event.nativeEvent.progress }); this.checkIfReady(); }; checkIfReady = () => { const { loadProgress, domLoaded } = this.state; // Only hide loader when progress hits 100% AND the DOM is ready if (loadProgress === 1 && domLoaded) { this.setState({ isLoading: false }); } }; // In your WebView JSX: <WebView source={{ uri: paymentUrl }} onLoad={this.handleDomLoad} onProgress={this.handleProgressUpdate} // Other props />
Solution 3: Track Navigation State (For Redirect-Heavy Flows)
If your payment flow involves multiple redirects, onLoadEnd might fire for each intermediate page. Use onNavigationStateChange to wait until you reach the final payment page before hiding the loader:
state = { isLoading: true, reachedFinalPage: false, }; handleNavigationChange = (navState) => { // Replace with your actual final payment page URL if (navState.url === 'https://your-payment-provider.com/confirmation') { this.setState({ reachedFinalPage: true }); } }; handlePageReady = (event) => { if (event.nativeEvent.data === 'pageLoaded' && this.state.reachedFinalPage) { this.setState({ isLoading: false }); } }; // In your WebView JSX: <WebView source={{ uri: paymentUrl }} onNavigationStateChange={this.handleNavigationChange} injectedJavaScript={` window.onload = function() { window.ReactNativeWebView.postMessage('pageLoaded'); }; true; `} onMessage={this.handlePageReady} // Other props />
Which Solution Should You Pick?
- Solution 1 is the most reliable because it directly uses the web page’s own load state, ensuring your background image is fully loaded before hiding the loader.
- Solution 2 is a solid fallback if you can’t inject scripts into the web page.
- Solution 3 is essential if your payment flow involves multiple redirects.
内容的提问来源于stack exchange,提问作者Sathya

