123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 |
- "use strict";
-
- import React, { PureComponent } from "react";
-
- import {
- findNodeHandle,
- requireNativeComponent,
- Animated,
- DeviceEventEmitter,
- Dimensions,
- StyleSheet,
- Platform,
- UIManager,
- View,
- ViewPropTypes,
- WebView
- } from "react-native";
-
- import PropTypes from "prop-types";
-
- import Immutable from "immutable";
-
- const RCTAutoHeightWebView = requireNativeComponent(
- "RCTAutoHeightWebView",
- AutoHeightWebView,
- { nativeOnly:
- {
- nativeOnly: {
- onLoadingStart: true,
- onLoadingError: true,
- onLoadingFinish: true,
- messagingEnabled: PropTypes.bool
- }
- }
- }
- );
-
- export default class AutoHeightWebView extends PureComponent {
- static propTypes = {
- source: WebView.propTypes.source,
- onHeightUpdated: PropTypes.func,
- customScript: PropTypes.string,
- customStyle: PropTypes.string,
- enableAnimation: PropTypes.bool,
- // if set to false may cause some layout issues (width of container will be than width of screen)
- scalesPageToFit: PropTypes.bool,
- // only works on enable animation
- animationDuration: PropTypes.number,
- // offset of rn webview margin
- heightOffset: PropTypes.number,
- // baseUrl not work in android 4.3 or below version
- enableBaseUrl: PropTypes.bool,
- style: ViewPropTypes.style,
- // rn WebView callback
- onError: PropTypes.func,
- onLoad: PropTypes.func,
- onLoadStart: PropTypes.func,
- onLoadEnd: PropTypes.func,
- // works if set enableBaseUrl to true; add web/files... to android/app/src/assets/
- files: PropTypes.arrayOf(
- PropTypes.shape({
- href: PropTypes.string,
- type: PropTypes.string,
- rel: PropTypes.string
- })
- )
- };
-
- static defaultProps = {
- scalesPageToFit: true,
- enableBaseUrl: false,
- enableAnimation: true,
- animationDuration: 555,
- heightOffset: 20
- };
-
- constructor(props) {
- super(props);
- this.onMessage = this.onMessage.bind(this);
- if (this.props.enableAnimation) {
- this.opacityAnimatedValue = new Animated.Value(0);
- }
- if (IsBelowKitKat) {
- this.listenWebViewBridgeMessage = this.listenWebViewBridgeMessage.bind(
- this
- );
- }
- let initialScript = props.files
- ? this.appendFilesToHead(props.files, BaseScript)
- : BaseScript;
- initialScript = props.customStyle
- ? this.appendStylesToHead(props.customStyle, initialScript)
- : initialScript;
- this.state = {
- isChangingSource: false,
- height: 0,
- heightOffset: 0,
- script: initialScript
- };
- }
-
- componentWillMount() {
- if (IsBelowKitKat) {
- DeviceEventEmitter.addListener(
- "webViewBridgeMessage",
- this.listenWebViewBridgeMessage
- );
- }
- }
-
- componentDidMount() {
- this.startInterval();
- }
-
- componentWillReceiveProps(nextProps) {
- // injectedJavaScript only works when webview reload (source changed)
- if (
- Immutable.is(
- Immutable.fromJS(this.props.source),
- Immutable.fromJS(nextProps.source)
- )
- ) {
- return;
- } else {
- this.setState(
- {
- isChangingSource: true,
- height: 0,
- heightOffset: 0
- },
- () => {
- this.startInterval();
- this.setState({ isChangingSource: false });
- }
- );
- }
- let currentScript = BaseScript;
- if (nextProps.files) {
- currentScript = this.appendFilesToHead(nextProps.files, BaseScript);
- }
- currentScript = nextProps.customStyle
- ? this.appendStylesToHead(nextProps.customStyle, currentScript)
- : currentScript;
- this.setState({ script: currentScript });
- }
-
- componentWillUnmount() {
- this.stopInterval();
- if (IsBelowKitKat) {
- DeviceEventEmitter.removeListener(
- "webViewBridgeMessage",
- this.listenWebViewBridgeMessage
- );
- }
- }
-
- // below kitkat
- listenWebViewBridgeMessage(body) {
- this.onMessage(body.message);
- }
-
- // below kitkat
- sendToWebView(message) {
- UIManager.dispatchViewManagerCommand(
- findNodeHandle(this.webview),
- UIManager.RCTAutoHeightWebView.Commands.sendToWebView,
- [String(message)]
- );
- }
-
- postMessage(data) {
- UIManager.dispatchViewManagerCommand(
- findNodeHandle(this.webview),
- UIManager.RCTAutoHeightWebView.Commands.postMessage,
- [String(data)]
- );
- }
-
- startInterval() {
- this.finishInterval = false;
- this.interval = setInterval(() => {
- if (!this.finishInterval) {
- IsBelowKitKat
- ? this.sendToWebView("getBodyHeight")
- : this.postMessage("getBodyHeight");
- }
- }, 205);
- }
-
- stopInterval() {
- this.finishInterval = true;
- clearInterval(this.interval);
- }
-
- onHeightUpdated(height) {
- if (this.props.onHeightUpdated) {
- this.props.onHeightUpdated(height);
- }
- }
-
- onMessage(e) {
- const height = parseInt(
- IsBelowKitKat ? e.nativeEvent.message : e.nativeEvent.data
- );
- if (height) {
- if (this.props.enableAnimation) {
- this.opacityAnimatedValue.setValue(0);
- }
- this.stopInterval();
- this.setState(
- {
- heightOffset: this.props.heightOffset,
- height
- },
- () => {
- if (this.props.enableAnimation) {
- Animated.timing(this.opacityAnimatedValue, {
- toValue: 1,
- duration: this.props.animationDuration
- }).start(() => this.onHeightUpdated(height));
- } else {
- this.onHeightUpdated(height);
- }
- }
- );
- }
- }
-
- appendFilesToHead(files, script) {
- if (!files) {
- return script;
- }
- return files.reduceRight((file, combinedScript) => `
- var link = document.createElement('link');
- link.rel = '${file.rel}';
- link.type = '${file.type}';
- link.href = '${file.href}';
- document.head.appendChild(link);
- ${combinedScript}
- `, script);
- }
-
- appendStylesToHead(styles, script) {
- if (!styles) {
- return script;
- }
- // Escape any single quotes or newlines in the CSS with .replace()
- const escaped = styles.replace(/\'/g, "\\'").replace(/\n/g, '\\n')
- return `
- var styleElement = document.createElement('style');
- var styleText = document.createTextNode('${escaped}');
- styleElement.appendChild(styleText);
- document.head.appendChild(styleElement);
- ${script}
- `;
- }
-
- onLoadingStart = (event) => {
- var onLoadStart = this.props.onLoadStart;
- onLoadStart && onLoadStart(event);
- };
-
- onLoadingError = (event) => {
- var {onError, onLoadEnd} = this.props;
- onError && onError(event);
- onLoadEnd && onLoadEnd(event);
- console.warn('Encountered an error loading page', event.nativeEvent);
- };
-
- onLoadingFinish = (event) => {
- var {onLoad, onLoadEnd} = this.props;
- onLoad && onLoad(event);
- onLoadEnd && onLoadEnd(event);
- };
-
- stopLoading() {
- UIManager.dispatchViewManagerCommand(
- findNodeHandle(this.webview),
- UIManager.RCTAutoHeightWebView.Commands.stopLoading,
- null
- );
- }
-
- render() {
- const { height, script, isChangingSource, heightOffset } = this.state;
- const {
- scalesPageToFit,
- enableAnimation,
- source,
- customScript,
- style,
- enableBaseUrl
- } = this.props;
- let webViewSource = source;
- if (enableBaseUrl) {
- webViewSource = Object.assign({}, source, {
- baseUrl: "file:///android_asset/web/"
- });
- }
- return (
- <Animated.View
- style={[
- Styles.container,
- {
- opacity: enableAnimation ? this.opacityAnimatedValue : 1,
- height: height + heightOffset
- },
- style
- ]}
- >
- {isChangingSource ? null : (
- <RCTAutoHeightWebView
- onLoadingStart={this.onLoadingStart}
- onLoadingFinish={this.onLoadingFinish}
- onLoadingError={this.onLoadingError}
- ref={webview => (this.webview = webview)}
- style={Styles.webView}
- javaScriptEnabled={true}
- injectedJavaScript={script + customScript}
- scalesPageToFit={scalesPageToFit}
- source={webViewSource}
- onMessage={this.onMessage}
- messagingEnabled={true}
- // below kitkat
- onChange={this.onMessage}
- />
- )}
- </Animated.View>
- );
- }
- }
-
- const ScreenWidth = Dimensions.get("window").width;
-
- const IsBelowKitKat = Platform.Version < 19;
-
- const Styles = StyleSheet.create({
- container: {
- width: ScreenWidth,
- backgroundColor: "transparent"
- },
- webView: {
- flex: 1,
- backgroundColor: "transparent"
- }
- });
-
- const BaseScript = IsBelowKitKat
- ? `
- ; (function () {
- AutoHeightWebView.onMessage = function (message) {
- AutoHeightWebView.send(String(document.body.offsetHeight));
- };
- } ());
- `
- : `
- ; (function () {
- document.addEventListener('message', function (e) {
- window.postMessage(String(document.body.offsetHeight));
- });
- } ());
- `;
|