Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When I use coverScreen=true, shut off the modal screen will flicker at the top #239

Open
MackJac opened this issue Aug 15, 2018 · 20 comments

Comments

@MackJac
Copy link

MackJac commented Aug 15, 2018

No description provided.

@MackJac
Copy link
Author

MackJac commented Aug 15, 2018

"react-native-modalbox": "^1.6.0"
"react": "16.3.1",
"react-native": "0.55.4",

@congdoan1
Copy link

I also got this error

@meiqi1992
Copy link

same issue! how to resolved?

@nes123
Copy link

nes123 commented Aug 20, 2018

same issue with IOS only

@nes123
Copy link

nes123 commented Aug 20, 2018

As a work around, do the following change in the index.js:

useNativeDriver: true => useNativeDriver: false

I hope it helps

@colinluond
Copy link

same issue. any chance to be resolved without a work around?

@MackJac
Copy link
Author

MackJac commented Aug 22, 2018

@nes123 thankyou,it's works,but the animation is not running well.
hope to have a best solution

@herarya
Copy link

herarya commented Aug 24, 2018

same issue +1

2 similar comments
@loveninteb
Copy link

same issue +1

@ldhios
Copy link

ldhios commented Sep 6, 2018

same issue +1

@claudioviola
Copy link

claudioviola commented Sep 6, 2018

same issue +1

iOS
react-native-cli: 2.0.1
react-native: 0.55.4
react-native-modalbox: 1.6.0

Uploaded a screencast here.
flickering_modal.zip

UPDATE:
On Android Real Device 8.0.0 works. no flickering occurred
On iOS Real Device 11.4.1 works too

@jayhack
Copy link

jayhack commented Sep 10, 2018

Hey all,

setting useNativeDriver: false in index.js solved this for us. I've forked the repo and you can install from here:

~$: npm install git+ssh://github.com/jayhack/react-native-modalbox

Cheers! 🍻

@nelson-glauber
Copy link

@jayhack solution worked for me. thanks! you saved my day.
@maxs15 do you have any plans to merge this change?

@noahtallen
Copy link

A similar issue on another modal package: react-native-modal/react-native-modal#92

I don't think useNativeDriver: false is a long term solution due to performance (which is already poor on low-powered android devices). I think it might ultimately be a react-native issue, though.

@msqar
Copy link

msqar commented Jan 28, 2019

A similar issue on another modal package: react-native-community/react-native-modal#92

I don't think useNativeDriver: false is a long term solution due to performance (which is already poor on low-powered android devices). I think it might ultimately be a react-native issue, though.

You think setting that is not okay? What should we do then? :/ i hate having that flickering, is super annoying.

@msqar
Copy link

msqar commented Jan 28, 2019

Seems that this is a react-native issue... in the react-native-modal github, they sent a fix for this. What should we do on modalbox? any idea guys?

@nes123
Copy link

nes123 commented Dec 18, 2019

this should be the fix:


import React from 'react';
import PropTypes from 'prop-types';
import {
View,
StyleSheet,
PanResponder,
Animated,
TouchableWithoutFeedback,
Dimensions,
Easing,
BackHandler,
Platform,
Modal,
Keyboard
} from 'react-native';

const {height: SCREEN_HEIGHT, width: SCREEN_WIDTH} = Dimensions.get('window');
const styles = StyleSheet.create({
wrapper: {
backgroundColor: 'white'
},
transparent: {
zIndex: 2,
backgroundColor: 'rgba(0,0,0,0)'
},
absolute: {
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0
}
});

export default class ModalBox extends React.PureComponent {
static propTypes = {
isOpen: PropTypes.bool,
isDisabled: PropTypes.bool,
startOpen: PropTypes.bool,
backdropPressToClose: PropTypes.bool,
swipeToClose: PropTypes.bool,
swipeThreshold: PropTypes.number,
swipeArea: PropTypes.number,
position: PropTypes.string,
entry: PropTypes.string,
backdrop: PropTypes.bool,
backdropOpacity: PropTypes.number,
backdropColor: PropTypes.string,
backdropContent: PropTypes.element,
animationDuration: PropTypes.number,
backButtonClose: PropTypes.bool,
easing: PropTypes.func,
coverScreen: PropTypes.bool,
keyboardTopOffset: PropTypes.number,
onClosed: PropTypes.func,
onOpened: PropTypes.func,
onClosingState: PropTypes.func
};

static defaultProps = {
startOpen: false,
backdropPressToClose: true,
swipeToClose: true,
swipeThreshold: 50,
position: 'center',
backdrop: true,
backdropOpacity: 0.5,
backdropColor: 'black',
backdropContent: null,
animationDuration: 400,
backButtonClose: false,
easing: Easing.elastic(0.8),
coverScreen: false,
keyboardTopOffset: Platform.OS == 'ios' ? 22 : 0,
useNativeDriver: true
};

constructor(props) {
super(props);

this.onBackPress = this.onBackPress.bind(this);
this.handleOpenning = this.handleOpenning.bind(this);
this.onKeyboardHide = this.onKeyboardHide.bind(this);
this.onKeyboardChange = this.onKeyboardChange.bind(this);
this.animateBackdropOpen = this.animateBackdropOpen.bind(this);
this.animateBackdropClose = this.animateBackdropClose.bind(this);
this.stopAnimateOpen = this.stopAnimateOpen.bind(this);
this.animateOpen = this.animateOpen.bind(this);
this.stopAnimateClose = this.stopAnimateClose.bind(this);
this.animateClose = this.animateClose.bind(this);
this.calculateModalPosition = this.calculateModalPosition.bind(this);
this.createPanResponder = this.createPanResponder.bind(this);
this.onViewLayout = this.onViewLayout.bind(this);
this.onContainerLayout = this.onContainerLayout.bind(this);
this.renderBackdrop = this.renderBackdrop.bind(this);
this.renderContent = this.renderContent.bind(this);
this.open = this.open.bind(this);
this.close = this.close.bind(this);

const position = props.startOpen
  ? new Animated.Value(0)
  : new Animated.Value(
      props.entry === 'top' ? -SCREEN_HEIGHT : SCREEN_HEIGHT
    );
this.state = {
  position,
  backdropOpacity: new Animated.Value(0),
  isOpen: props.startOpen,
  isAnimateClose: false,
  isAnimateOpen: false,
  swipeToClose: false,
  height: SCREEN_HEIGHT,
  width: SCREEN_WIDTH,
  containerHeight: SCREEN_HEIGHT,
  containerWidth: SCREEN_WIDTH,
  isInitialized: false,
  keyboardOffset: 0,
  pan: this.createPanResponder(position),
  hideContent: false,
};

// Needed for iOS because the keyboard covers the screen
if (Platform.OS === 'ios') {
  this.subscriptions = [
    Keyboard.addListener('keyboardWillChangeFrame', this.onKeyboardChange),
    Keyboard.addListener('keyboardDidHide', this.onKeyboardHide)
  ];
}

}

componentDidMount() {
this.handleOpenning();
}

componentDidUpdate(prevProps) {
if (this.props.isOpen != prevProps.isOpen) {
this.handleOpenning();
}
}

componentWillUnmount() {
if (this.subscriptions) this.subscriptions.forEach(sub => sub.remove());
if (this.props.backButtonClose && Platform.OS === 'android')
BackHandler.removeEventListener('hardwareBackPress', this.onBackPress);
}

onBackPress() {
this.close();
return true;
}

handleOpenning() {
if (typeof this.props.isOpen == 'undefined') return;
if (this.props.isOpen) this.open();
else this.close();
}

/****************** ANIMATIONS **********************/

/*

  • The keyboard is hidden (IOS only)
    */
    onKeyboardHide(evt) {
    this.setState({keyboardOffset: 0});
    }

/*

  • The keyboard frame changed, used to detect when the keyboard open, faster than keyboardDidShow (IOS only)
    */
    onKeyboardChange(evt) {
    if (!evt) return;
    if (!this.state.isOpen) return;
    const keyboardFrame = evt.endCoordinates;
    const keyboardHeight = this.state.containerHeight - keyboardFrame.screenY;
this.setState({keyboardOffset: keyboardHeight}, () => {
  this.animateOpen();
});

}

/*

  • Open animation for the backdrop, will fade in
    */
    animateBackdropOpen() {
    if (this.state.isAnimateBackdrop && this.state.animBackdrop) {
    this.state.animBackdrop.stop();
    }
    this.setState({isAnimateBackdrop: true});
let animBackdrop = Animated.timing(this.state.backdropOpacity, {
  toValue: 1,
  duration: this.props.animationDuration,
  easing: this.props.easing,
  useNativeDriver: this.props.useNativeDriver
}).start(() => {
  this.setState({
    isAnimateBackdrop: false,
    animBackdrop
  });
});

}

/*

  • Close animation for the backdrop, will fade out
    */
    animateBackdropClose() {
    if (this.state.isAnimateBackdrop && this.state.animBackdrop) {
    this.state.animBackdrop.stop();
    }
    this.setState({isAnimateBackdrop: true});
let animBackdrop = Animated.timing(this.state.backdropOpacity, {
  toValue: 0,
  duration: this.props.animationDuration,
  easing: this.props.easing,
  useNativeDriver: this.props.useNativeDriver
}).start(() => {
  this.setState({
    isAnimateBackdrop: false,
    animBackdrop
  });
});

}

/*

  • Stop opening animation
    */
    stopAnimateOpen() {
    if (this.state.isAnimateOpen) {
    if (this.state.animOpen) this.state.animOpen.stop();
    this.setState({isAnimateOpen: false});
    }
    }

/*

  • Open animation for the modal, will move up
    */
    animateOpen() {
    this.stopAnimateClose();
// Backdrop fadeIn
if (this.props.backdrop) this.animateBackdropOpen();

this.setState(
  {
    isAnimateOpen: true,
    isOpen: true
  },
  () => {
    requestAnimationFrame(() => {
      // Detecting modal position
      let positionDest = this.calculateModalPosition(
        this.state.containerHeight - this.state.keyboardOffset,
        this.state.containerWidth
      );
      if (
        this.state.keyboardOffset &&
        positionDest < this.props.keyboardTopOffset
      ) {
        positionDest = this.props.keyboardTopOffset;
      }
      let animOpen = Animated.timing(this.state.position, {
        toValue: positionDest,
        duration: this.props.animationDuration,
        easing: this.props.easing,
        useNativeDriver: this.props.useNativeDriver
      }).start(() => {
        this.setState({
          isAnimateOpen: false,
          animOpen,
          positionDest
        });
        if (this.props.onOpened) this.props.onOpened();
      });
    });
  }
);

}

/*

  • Stop closing animation
    */
    stopAnimateClose() {
    if (this.state.isAnimateClose) {
    if (this.state.animClose) this.state.animClose.stop();
    this.setState({hideContent: true}, () =>
    this.setState({isAnimateClose: false}, () => {
    this.setState({hideContent: false});
    })
    );
    }
    }

/*

  • Close animation for the modal, will move down
    */
    animateClose() {
    this.stopAnimateOpen();
// Backdrop fadeout
if (this.props.backdrop) this.animateBackdropClose();

this.setState(
  {
    isAnimateClose: true,
    isOpen: false
  },
  () => {
    let animClose = Animated.timing(this.state.position, {
      toValue:
        this.props.entry === 'top'
          ? -this.state.containerHeight
          : this.state.containerHeight,
      duration: this.props.animationDuration,
      easing: this.props.easing,
      useNativeDriver: this.props.useNativeDriver
    }).start(() => {
      // Keyboard.dismiss();   // make this optional. Easily user defined in .onClosed() callback
      this.setState({hideContent: true}, () => {
      this.setState({
        isAnimateClose: false,
        animClose
      }, () => {
        this.setState({hideContent: false});
        /* Set the state to the starting position of the modal, preventing from animating where the swipe stopped */
        this.state.position.setValue(this.props.entry === 'top' ? -this.state.containerHeight : this.state.containerHeight);
      });
      });
      if (this.props.onClosed) this.props.onClosed();
    });
  }
);

}

/*

  • Calculate when should be placed the modal
    */
    calculateModalPosition(containerHeight, containerWidth) {
    let position = 0;
if (this.props.position == 'bottom') {
  position = containerHeight - this.state.height;
} else if (this.props.position == 'center') {
  position = containerHeight / 2 - this.state.height / 2;
}
// Checking if the position >= 0
if (position < 0) position = 0;
return position;

}

/*

  • Create the pan responder to detect gesture
  • Only used if swipeToClose is enabled
    */
    createPanResponder(position) {
    let closingState = false;
    let inSwipeArea = false;
const onPanStart = (evt, state) => {
  if (
    !this.props.swipeToClose ||
    this.props.isDisabled ||
    (this.props.swipeArea &&
      evt.nativeEvent.pageY - this.state.positionDest >
        this.props.swipeArea)
  ) {
    inSwipeArea = false;
    return false;
  }
  inSwipeArea = true;
  return true;
};

const animEvt = Animated.event([null, {customY: position}]);

const onPanMove = (evt, state) => {
  const newClosingState =
    this.props.entry === 'top'
      ? -state.dy > this.props.swipeThreshold
      : state.dy > this.props.swipeThreshold;
  if (this.props.entry === 'top' ? state.dy > 0 : state.dy < 0) return;
  if (newClosingState != closingState && this.props.onClosingState)
    this.props.onClosingState(newClosingState);
  closingState = newClosingState;
  state.customY = state.dy + this.state.positionDest;

  animEvt(evt, state);
};

const onPanRelease = (evt, state) => {
  if (!inSwipeArea) return;
  inSwipeArea = false;
  if (
    this.props.entry === 'top'
      ? -state.dy > this.props.swipeThreshold
      : state.dy > this.props.swipeThreshold
  ) {
    this.close();
  } else if (!this.state.isOpen) {
    this.animateOpen();
  }
};

return PanResponder.create({
  onStartShouldSetPanResponder: onPanStart,
  onPanResponderMove: onPanMove,
  onPanResponderRelease: onPanRelease,
  onPanResponderTerminate: onPanRelease
});

}

/*

  • Event called when the modal view layout is calculated
    */
    onViewLayout(evt) {
    const height = evt.nativeEvent.layout.height;
    const width = evt.nativeEvent.layout.width;
// If the dimensions are still the same we're done
let newState = {};
if (height !== this.state.height) newState.height = height;
if (width !== this.state.width) newState.width = width;
this.setState(newState);

if (this.onViewLayoutCalculated) this.onViewLayoutCalculated();

}

/*

  • Event called when the container view layout is calculated
    */
    onContainerLayout(evt) {
    const height = evt.nativeEvent.layout.height;
    const width = evt.nativeEvent.layout.width;
// If the container size is still the same we're done
if (
  height == this.state.containerHeight &&
  width == this.state.containerWidth
) {
  this.setState({isInitialized: true});
  return;
}

if (this.state.isOpen || this.state.isAnimateOpen) {
  this.animateOpen();
}

if (this.props.onLayout) this.props.onLayout(evt);
this.setState({
  isInitialized: true,
  containerHeight: height,
  containerWidth: width
});

}

/*

  • Render the backdrop element
    */
    renderBackdrop() {
    let backdrop = null;
if (this.props.backdrop) {
  backdrop = (
    <TouchableWithoutFeedback
      onPress={this.props.backdropPressToClose ? this.close : null}>
      <Animated.View
        importantForAccessibility="no"
        style={[styles.absolute, {opacity: this.state.backdropOpacity}]}>
        <View
          style={[
            styles.absolute,
            {
              backgroundColor: this.props.backdropColor,
              opacity: this.props.backdropOpacity
            }
          ]}
        />
        {this.props.backdropContent || []}
      </Animated.View>
    </TouchableWithoutFeedback>
  );
}

return backdrop;

}

renderContent() {
const size = {
height: this.state.containerHeight,
width: this.state.containerWidth
};
const offsetX = (this.state.containerWidth - this.state.width) / 2;

return (
  <Animated.View
    onLayout={this.onViewLayout}
    style={[
      styles.wrapper,
      size,
      this.props.style,
      {
        transform: [
          {translateY: this.state.position},
          {translateX: offsetX}
        ]
      }
    ]}
    {...this.state.pan.panHandlers}>
    {this.props.children}
  </Animated.View>
);

}

/*

  • Render the component
    */
    render() {
    const visible =
    this.state.isOpen ||
    this.state.isAnimateOpen ||
    this.state.isAnimateClose;
if (!visible) return <View />;

const content = (
  <View
    importantForAccessibility="yes"
    accessibilityViewIsModal={true}
    style={[styles.transparent, styles.absolute]}
    pointerEvents={'box-none'}>
    <View
      style={{flex: 1}}
      pointerEvents={'box-none'}
      onLayout={this.onContainerLayout}>
      {visible && this.renderBackdrop()}
      {visible && this.renderContent()}
    </View>
  </View>
);

if (!this.props.coverScreen) return content;

return (
  <Modal
    onRequestClose={() => {
      if (this.props.backButtonClose) {
        this.close();
      }
    }}
    supportedOrientations={[
      'landscape',
      'portrait',
      'portrait-upside-down'
    ]}
    transparent
    visible={visible}
    hardwareAccelerated={true}>
    {this.state.hideContent ? null : content}
  </Modal>
);

}

/****************** PUBLIC METHODS **********************/

open() {
if (this.props.isDisabled) return;
if (
!this.state.isAnimateOpen &&
(!this.state.isOpen || this.state.isAnimateClose)
) {
this.onViewLayoutCalculated = () => {
this.animateOpen();
if (this.props.backButtonClose && Platform.OS === 'android')
BackHandler.addEventListener('hardwareBackPress', this.onBackPress);
this.onViewLayoutCalculated = null;
};
this.setState({isAnimateOpen: true});
}
}

close() {
if (this.props.isDisabled) return;
if (
!this.state.isAnimateClose &&
(this.state.isOpen || this.state.isAnimateOpen)
) {
this.animateClose();
if (this.props.backButtonClose && Platform.OS === 'android')
BackHandler.removeEventListener('hardwareBackPress', this.onBackPress);
}
}
}


@Dylan0916
Copy link

Same issue.
any updates?

@khoatran808
Copy link

Same issue

"react": "16.13.1",
"react-native": "0.62.0",
"react-native-modalbox": "2.0.0",

@mehmetnyarar
Copy link

mehmetnyarar commented Mar 4, 2021

@nes123 why not creating a PR instead?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests