Open dbuarque opened 8 years ago
Hi @dbuarque, did you ever find a solution to this problem? I've been fighting the same issue all weekend now, with little success.
yes, I implemented my own lightbox component
@dbuarque Does you lightbox component available somewhere ? :) Thanks in advance !
Hi @pcrouillere, not yet, I need some time to prepare to release
I am also waiting for that lightbox component so I could use it in my projects
@dbuarque I'd love to have it, too. Even if you don't publish it as an actual react-native component, just the code is better than nothing !
Hi
is there any solution for orientation?
I solved it by adding an event listener to Dimensions and changing device width and height whenever the orientation changes. Following is the altered LightboxOverlay.js:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {
Animated,
Dimensions,
Modal,
PanResponder,
Platform,
StatusBar,
StyleSheet,
Text,
TouchableOpacity,
View
} from 'react-native'
const DRAG_DISMISS_THRESHOLD = 150
const STATUS_BAR_OFFSET = Platform.OS === 'android' ? -25 : 0
const isIOS = Platform.OS === 'ios'
const styles = StyleSheet.create({
background: {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%'
},
open: {
position: 'absolute',
flex: 1,
justifyContent: 'center',
// Android pan handlers crash without this declaration:
backgroundColor: 'transparent'
},
header: {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
backgroundColor: 'transparent'
},
closeButton: {
fontSize: 35,
color: 'white',
lineHeight: 40,
width: 40,
textAlign: 'center',
shadowOffset: {
width: 0,
height: 0
},
shadowRadius: 1.5,
shadowColor: 'black',
shadowOpacity: 0.8
}
})
export default class LightboxOverlay extends Component {
static propTypes = {
origin: PropTypes.shape({
x: PropTypes.number,
y: PropTypes.number,
width: PropTypes.number,
height: PropTypes.number
}),
springConfig: PropTypes.shape({
tension: PropTypes.number,
friction: PropTypes.number
}),
backgroundColor: PropTypes.string,
isOpen: PropTypes.bool,
renderHeader: PropTypes.func,
onOpen: PropTypes.func,
onClose: PropTypes.func,
swipeToDismiss: PropTypes.bool
}
static defaultProps = {
springConfig: { tension: 30, friction: 7 },
backgroundColor: 'black'
}
constructor(props) {
super(props)
this.state = {
isAnimating: false,
isPanning: false,
target: {
x: 0,
y: 0,
opacity: 1
},
pan: new Animated.Value(0),
openVal: new Animated.Value(0)
}
Dimensions.addEventListener('change', () => {
this.setState({
windowHeight: Dimensions.get('window').height,
windowWidth: Dimensions.get('window').width
})
})
}
componentWillMount() {
this._panResponder = PanResponder.create({
// Ask to be the responder:
onStartShouldSetPanResponder: (evt, gestureState) => !this.state.isAnimating,
onStartShouldSetPanResponderCapture: (evt, gestureState) => !this.state.isAnimating,
onMoveShouldSetPanResponder: (evt, gestureState) => !this.state.isAnimating,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => !this.state.isAnimating,
onPanResponderGrant: (evt, gestureState) => {
this.state.pan.setValue(0)
this.setState({ isPanning: true })
},
onPanResponderMove: Animated.event([null, { dy: this.state.pan }]),
onPanResponderTerminationRequest: (evt, gestureState) => true,
onPanResponderRelease: (evt, gestureState) => {
if (Math.abs(gestureState.dy) > DRAG_DISMISS_THRESHOLD) {
this.setState({
isPanning: false,
target: {
y: gestureState.dy,
x: gestureState.dx,
opacity: 1 - Math.abs(gestureState.dy / this.state.windowHeight)
}
})
this.close()
} else {
Animated.spring(this.state.pan, { toValue: 0, ...this.props.springConfig }).start(() => {
this.setState({ isPanning: false })
})
}
}
})
}
componentDidMount() {
if (this.props.isOpen) {
this.open()
}
}
open = () => {
if (isIOS) {
StatusBar.setHidden(true, 'fade')
}
this.state.pan.setValue(0)
this.setState({
isAnimating: true,
target: {
x: 0,
y: 0,
opacity: 1
}
})
Animated.spring(this.state.openVal, { toValue: 1, ...this.props.springConfig }).start(() =>
this.setState({ isAnimating: false })
)
}
close = () => {
if (isIOS) {
StatusBar.setHidden(false, 'fade')
}
this.setState({
isAnimating: true
})
Animated.spring(this.state.openVal, { toValue: 0, ...this.props.springConfig }).start(() => {
this.setState({
isAnimating: false
})
this.props.onClose()
})
}
componentWillReceiveProps(props) {
if (this.props.isOpen != props.isOpen && props.isOpen) {
this.open()
}
}
render() {
const { isOpen, renderHeader, swipeToDismiss, origin, backgroundColor } = this.props
const { isPanning, isAnimating, openVal, target } = this.state
const lightboxOpacityStyle = {
opacity: openVal.interpolate({ inputRange: [0, 1], outputRange: [0, target.opacity] })
}
let handlers
if (swipeToDismiss) {
handlers = this._panResponder.panHandlers
}
let dragStyle
if (isPanning) {
dragStyle = {
top: this.state.pan
}
lightboxOpacityStyle.opacity = this.state.pan.interpolate({
inputRange: [-this.state.windowHeight, 0, this.state.windowHeight],
outputRange: [0, 1, 0]
})
}
const openStyle = [
styles.open,
{
left: openVal.interpolate({ inputRange: [0, 1], outputRange: [origin.x, target.x] }),
top: openVal.interpolate({
inputRange: [0, 1],
outputRange: [origin.y + STATUS_BAR_OFFSET, target.y + STATUS_BAR_OFFSET]
}),
width: openVal.interpolate({
inputRange: [0, 1],
outputRange: [origin.width, this.state.windowWidth]
}),
height: openVal.interpolate({
inputRange: [0, 1],
outputRange: [origin.height, this.state.windowHeight]
})
}
]
const background = (
<Animated.View
style={[styles.background, { backgroundColor: backgroundColor }, lightboxOpacityStyle]}
/>
)
const header = (
<Animated.View style={[styles.header, lightboxOpacityStyle]}>
{renderHeader ? (
renderHeader(this.close)
) : (
<TouchableOpacity onPress={this.close}>
<Text style={styles.closeButton}>×</Text>
</TouchableOpacity>
)}
</Animated.View>
)
const content = (
<Animated.View style={[openStyle, dragStyle]} {...handlers}>
{this.props.children}
</Animated.View>
)
if (this.props.navigator) {
return (
<View>
{background}
{content}
{header}
</View>
)
}
return (
<Modal visible={isOpen} transparent={true} onRequestClose={() => this.close()}>
{background}
{content}
{header}
</Modal>
)
}
}
Edit:
I pass activeProps as follows:
activeProps={{
style: {
flex: 1,
width: '100%',
height: '100%'
}
}}
Hi @oblador, any update on the above issue it's almost like 4 years old bug and still no resolution. Did any one find a solution for the above bug ?
I found a solution which uses 2 new values named deviceWidth and deviceHeight which are wrapped in a useState hook. Also I added a useEffect hook which listens to event changes.
If you paste this whole code block into LightboxOverlay.js then it should work.
node_modules > react-native-lightbox > dist > LightboxOverlay.js
I will paste the whole code and the parts I added below:
import React, { useRef, useEffect, useState } from "react";
import { Animated, Dimensions, PanResponder, Platform, StyleSheet, StatusBar, TouchableOpacity, Text, Modal, } from "react-native";
import { useGesture, useNextTick } from "./hooks";
const { width: WINDOW_WIDTH, height: WINDOW_HEIGHT } = Dimensions.get("window");
const isIOS = Platform.OS === "ios";
const getDefaultTarget = () => ({ x: 0, y: 0, opacity: 1 });
const styles = StyleSheet.create({
background: {
position: "absolute",
top: 0,
left: 0,
width: WINDOW_WIDTH,
height: WINDOW_HEIGHT,
},
open: {
position: "absolute",
flex: 1,
justifyContent: "center",
// Android pan handlers crash without this declaration:
backgroundColor: "transparent",
},
header: {
position: "absolute",
top: 0,
left: 0,
width: WINDOW_WIDTH,
backgroundColor: "transparent",
},
closeButton: {
fontSize: 35,
color: "white",
lineHeight: 60,
width: 70,
textAlign: "center",
shadowOffset: {
width: 0,
height: 0,
},
shadowRadius: 1.5,
shadowColor: "black",
shadowOpacity: 0.8,
},
});
const LightboxOverlay = ({ useNativeDriver, dragDismissThreshold, springConfig, isOpen, onClose, willClose, didOpen, swipeToDismiss, origin, backgroundColor, renderHeader, modalProps, children, doubleTapZoomEnabled, doubleTapGapTimer, doubleTapCallback, doubleTapZoomToCenter, doubleTapMaxZoom, doubleTapZoomStep, doubleTapInitialScale, doubleTapAnimationDuration, longPressGapTimer, longPressCallback }) => {
const _panResponder = useRef();
const pan = useRef(new Animated.Value(0));
const openVal = useRef(new Animated.Value(0));
const handlers = useRef();
const [gesture, animations] = useGesture({
useNativeDriver,
doubleTapZoomEnabled,
doubleTapGapTimer,
doubleTapCallback,
doubleTapZoomToCenter,
doubleTapMaxZoom,
doubleTapZoomStep,
doubleTapInitialScale,
doubleTapAnimationDuration,
longPressGapTimer,
longPressCallback
});
const [deviceWidth, setDeviceWidth] = useState(Dimensions.get('window').width);
const [deviceHeight, setDeviceHeight] = useState(Dimensions.get('window').height);
const [{ isAnimating, isPanning, target }, setState] = useState({
isAnimating: false,
isPanning: false,
target: getDefaultTarget(),
});
const handleCloseNextTick = useNextTick(onClose);
const close = () => {
willClose();
if (isIOS) {
StatusBar.setHidden(false, "fade");
}
gesture.reset();
setState((s) => ({
...s,
isAnimating: true,
}));
Animated.spring(openVal.current, {
toValue: 0,
...springConfig,
useNativeDriver,
}).start(({ finished }) => {
if (finished) {
setState((s) => ({ ...s, isAnimating: false }));
handleCloseNextTick();
}
});
};
const open = () => {
if (isIOS) {
StatusBar.setHidden(true, "fade");
}
pan.current.setValue(0);
setState((s) => ({
...s,
isAnimating: true,
target: getDefaultTarget(),
}));
Animated.spring(openVal.current, {
toValue: 1,
...springConfig,
useNativeDriver,
}).start(({ finished }) => {
if (finished) {
setState((s) => ({ ...s, isAnimating: false }));
didOpen();
}
});
};
const initPanResponder = () => {
_panResponder.current = PanResponder.create({
// Ask to be the responder:
onStartShouldSetPanResponder: () => !isAnimating,
onStartShouldSetPanResponderCapture: () => !isAnimating,
onMoveShouldSetPanResponder: () => !isAnimating,
onMoveShouldSetPanResponderCapture: () => !isAnimating,
onPanResponderGrant: (e, gestureState) => {
gesture.init();
pan.current.setValue(0);
setState((s) => ({ ...s, isPanning: true }));
gesture.onLongPress(e, gestureState);
gesture.onDoubleTap(e, gestureState);
},
onPanResponderMove: Animated.event([null, { dy: pan.current }], {
useNativeDriver,
}),
onPanResponderTerminationRequest: () => true,
onPanResponderRelease: (evt, gestureState) => {
gesture.release();
if (gesture.isDoubleTaped)
return;
if (gesture.isLongPressed)
return;
if (Math.abs(gestureState.dy) > dragDismissThreshold) {
setState((s) => ({
...s,
isPanning: false,
target: {
y: gestureState.dy,
x: gestureState.dx,
opacity: 1 - Math.abs(gestureState.dy / WINDOW_HEIGHT),
},
}));
close();
}
else {
Animated.spring(pan.current, {
toValue: 0,
...springConfig,
useNativeDriver,
}).start(({ finished }) => {
finished && setState((s) => ({ ...s, isPanning: false }));
});
}
},
});
};
useEffect(() => {
initPanResponder();
}, [useNativeDriver, isAnimating]);
useEffect(() => {
const onChange = ({ window }) => {
setDeviceWidth(window.width);
setDeviceHeight(window.height);
};
const removeEventListener = () => {
if (Dimensions.removeEventListener) {
Dimensions.removeEventListener('change', onChange);
}
};
Dimensions.addEventListener('change', onChange);
return removeEventListener;
}, []);
useEffect(() => {
isOpen && open();
}, [isOpen]);
useEffect(() => {
if (_panResponder.current && swipeToDismiss) {
handlers.current = _panResponder.current.panHandlers;
}
}, [swipeToDismiss, _panResponder.current]);
const lightboxOpacityStyle = {
opacity: openVal.current.interpolate({
inputRange: [0, 1],
outputRange: [0, target.opacity],
}),
};
let dragStyle;
if (isPanning) {
dragStyle = {
top: pan.current,
};
lightboxOpacityStyle.opacity = pan.current.interpolate({
inputRange: [-WINDOW_HEIGHT, 0, WINDOW_HEIGHT],
outputRange: [0, 1, 0],
});
}
const openStyle = [
styles.open,
{
left: openVal.current.interpolate({
inputRange: [0, 1],
outputRange: [origin.x, target.x],
}),
top: openVal.current.interpolate({
inputRange: [0, 1],
outputRange: [origin.y, target.y],
}),
width: openVal.current.interpolate({
inputRange: [0, 1],
outputRange: [origin.width, deviceWidth],
}),
height: openVal.current.interpolate({
inputRange: [0, 1],
outputRange: [origin.height, deviceHeight],
}),
},
];
const background = (<Animated.View style={[styles.background, {
backgroundColor,
width: deviceWidth,
height: deviceHeight,
}, lightboxOpacityStyle]}></Animated.View>);
const header = (<Animated.View style={[styles.header, lightboxOpacityStyle]}>
{renderHeader ? (renderHeader(close)) : (<TouchableOpacity onPress={close}>
<Text style={styles.closeButton}>×</Text>
</TouchableOpacity>)}
</Animated.View>);
const content = (<Animated.View style={[openStyle, dragStyle, animations]} {...handlers.current}>
{children}
</Animated.View>);
return (<Modal visible={isOpen} transparent={true} onRequestClose={close} {...modalProps}>
{background}
{content}
{header}
</Modal>);
};
export default LightboxOverlay;
useEffect hook for listening for changes in window.width and window.height
useEffect(() => {
const onChange = ({ window }) => {
setDeviceWidth(window.width);
setDeviceHeight(window.height);
};
const removeEventListener = () => {
if (Dimensions.removeEventListener) {
Dimensions.removeEventListener('change', onChange);
}
};
Dimensions.addEventListener('change', onChange);
return removeEventListener;
}, []);
useState hooks for deviceWidth and deviceHeight
const [deviceWidth, setDeviceWidth] = useState(Dimensions.get('window').width);
const [deviceHeight, setDeviceHeight] = useState(Dimensions.get('window').height);
Then in openStyle, in width and height I changed the outputRange to use deviceWidth and deviceHeight
const openStyle = [
styles.open,
{
left: openVal.current.interpolate({
inputRange: [0, 1],
outputRange: [origin.x, target.x],
}),
top: openVal.current.interpolate({
inputRange: [0, 1],
outputRange: [origin.y, target.y],
}),
width: openVal.current.interpolate({
inputRange: [0, 1],
outputRange: [origin.width, deviceWidth],
}),
height: openVal.current.interpolate({
inputRange: [0, 1],
outputRange: [origin.height, deviceHeight],
}),
},
];
Hey guys,
I am try to fix an issue when switch the device orientation. I have tried to use the activeProps but it only change the size of the image while is necessary to change the LightboxOvelay also.
Any idea how to fix this?