react-native-page-flipper
Version:
300 lines (277 loc) • 8.77 kB
JavaScript
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Pressable, StyleSheet, View } from 'react-native';
import { PanGestureHandler } from 'react-native-gesture-handler';
import Animated, { Easing, Extrapolate, interpolate, runOnJS, useAnimatedGestureHandler, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming } from 'react-native-reanimated';
import BackShadow from '../BookPage/BackShadow';
import FrontShadow from '../BookPage/FrontShadow';
import PageShadow from '../BookPage/PageShadow';
import { clamp, snapPoint } from '../utils/utils';
const timingConfig = {
duration: 800,
easing: Easing.inOut(Easing.cubic)
};
const BookPagePortrait = /*#__PURE__*/React.forwardRef((_ref, ref) => {
let {
current,
prev,
onPageFlip,
containerSize,
enabled,
isPressable,
setIsAnimating,
getPageStyle,
isAnimating,
isAnimatingRef,
next,
onFlipStart,
onPageDrag,
onPageDragEnd,
onPageDragStart,
renderPage
} = _ref;
const containerWidth = containerSize.width;
const pSnapPoints = !prev ? [-containerSize.width, 0] : [-containerSize.width, 0, containerSize.width];
const x = useSharedValue(0);
const isMounted = useRef(false);
const rotateYAsDeg = useSharedValue(0); // might not need this
// useEffect(() => {
// if (!enabled) {
// setIsDragging(false);
// }
// }, [enabled]);
const turnPage = useCallback(id => {
setIsAnimating(true);
if (onFlipStart && typeof onFlipStart === 'function') {
onFlipStart(id);
}
rotateYAsDeg.value = withTiming(id < 0 ? -180 : 180, timingConfig, () => {
runOnJS(onPageFlip)(id, false);
});
}, [onFlipStart, onPageFlip, rotateYAsDeg, setIsAnimating]);
React.useImperativeHandle(ref, () => ({
turnPage
}), [turnPage]);
useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
const getDegreesForX = x => {
'worklet';
const val = interpolate(x, [-containerSize.width, 0, containerSize.width], [180, 0, -180], Extrapolate.CLAMP);
return val;
};
const containerStyle = useAnimatedStyle(() => {
return {
flex: 1
};
});
const onPanGestureHandler = useAnimatedGestureHandler({
// @ts-ignore
onStart: (event, ctx) => {
ctx.x = x.value;
if (onPageDragStart && typeof onPageDragStart === 'function') {
runOnJS(onPageDragStart)();
}
},
onActive: (event, ctx) => {
const newX = ctx.x + event.translationX;
const degrees = getDegreesForX(newX);
x.value = newX;
rotateYAsDeg.value = degrees;
if (onPageDrag && typeof onPageDrag === 'function') {
runOnJS(onPageDrag)();
}
},
onEnd: event => {
if (onPageDragEnd && typeof onPageDragEnd === 'function') {
runOnJS(onPageDragEnd)();
}
const snapTo = snapPoint(x.value, event.velocityX, pSnapPoints);
const id = snapTo > 0 ? -1 : snapTo < 0 ? 1 : 0;
if (!next && id > 0) {
// reset
x.value = withTiming(0);
rotateYAsDeg.value = withTiming(0); // runOnJS(onDrag)(false);
return;
}
const degrees = getDegreesForX(snapTo);
x.value = snapTo;
if (rotateYAsDeg.value === degrees) {
// already same value
// debugValue('already there');
runOnJS(onPageFlip)(id, false);
} else {
runOnJS(setIsAnimating)(true);
const progress = Math.abs(rotateYAsDeg.value - degrees) / 100;
const duration = clamp(800 * progress - Math.abs(0.1 * event.velocityX), 350, 1000);
rotateYAsDeg.value = withTiming(degrees, { ...timingConfig,
duration: duration
}, () => {
if (snapTo === 0) {//
}
runOnJS(onPageFlip)(id, false);
});
}
}
});
const gesturesEnabled = enabled && !isAnimating;
const iPageProps = {
containerSize,
containerWidth,
getPageStyle,
rotateYAsDeg,
renderPage
};
return /*#__PURE__*/React.createElement(Animated.View, {
style: containerStyle
}, /*#__PURE__*/React.createElement(PanGestureHandler, {
onGestureEvent: onPanGestureHandler,
enabled: gesturesEnabled
}, /*#__PURE__*/React.createElement(Animated.View, {
style: containerStyle
}, isPressable && prev && /*#__PURE__*/React.createElement(Pressable, {
disabled: isAnimating,
onPress: () => {
if (!isAnimatingRef.current) turnPage(-1);
},
style: {
position: 'absolute',
height: '100%',
width: '25%',
zIndex: 10000,
left: 0 // backgroundColor: 'red',
// opacity: 0.2,
}
}), isPressable && next && /*#__PURE__*/React.createElement(Pressable, {
disabled: isAnimating,
onPress: () => {
if (!isAnimatingRef.current) turnPage(1);
},
style: {
position: 'absolute',
height: '100%',
width: '30%',
zIndex: 10000,
right: 0 // backgroundColor: 'blue',
// opacity: 0.2,
}
}), current && next ? /*#__PURE__*/React.createElement(IPage, _extends({
page: current,
right: true
}, iPageProps)) : /*#__PURE__*/React.createElement(View, {
style: {
height: '100%',
width: '100%'
}
}, renderPage && /*#__PURE__*/React.createElement(View, {
style: getPageStyle(true, true)
}, renderPage(current.right))), prev && /*#__PURE__*/React.createElement(IPage, _extends({
page: prev,
right: false
}, iPageProps)))));
});
const IPage = _ref2 => {
let {
right,
page,
rotateYAsDeg,
containerWidth,
containerSize,
getPageStyle,
renderPage
} = _ref2;
const [loaded, setLoaded] = useState(right);
useEffect(() => {
// hack fix
setTimeout(() => {
setLoaded(true);
}, 50);
}, []);
const rotationVal = useDerivedValue(() => {
const val = right ? rotateYAsDeg.value : interpolate(rotateYAsDeg.value, [-180, 0], [0, 180]);
return val;
});
const portraitBackStyle = useAnimatedStyle(() => {
const x = interpolate(rotationVal.value, [0, 180], [containerWidth, -containerWidth / 2]);
const w = interpolate(rotationVal.value, [0, 180], [0, containerWidth / 2]);
return {
width: Math.ceil(w),
zIndex: 2,
opacity: 1,
transform: [{
translateX: x
}]
};
});
const portraitFrontStyle = useAnimatedStyle(() => {
const w = interpolate(rotationVal.value, [0, 160], [containerWidth, -20], Extrapolate.CLAMP);
const style = {
zIndex: 1,
width: Math.floor(w)
};
if (!right) {
style['left'] = 0;
} else {// style['right'] = 0;
}
return style;
});
const frontPageStyle = getPageStyle(right, true);
const backPageStyle = getPageStyle(right, false);
if (!loaded) {
// hack fix
return null;
}
const shadowProps = {
right: true,
degrees: rotationVal,
width: containerSize.width,
viewHeight: containerSize.height
};
return /*#__PURE__*/React.createElement(View, {
style: { ...StyleSheet.absoluteFillObject,
zIndex: !right ? 5 : 0
}
}, /*#__PURE__*/React.createElement(Animated.View, {
style: [styles.pageContainer, portraitBackStyle, {
overflow: 'visible'
}]
}, /*#__PURE__*/React.createElement(View, {
style: styles.pageContainer
}, renderPage && /*#__PURE__*/React.createElement(Animated.View, {
style: [backPageStyle, {
opacity: 0.2,
transform: [{
rotateX: '180deg'
}, {
rotateZ: '180deg'
}]
}]
}, renderPage(page.left))), /*#__PURE__*/React.createElement(BackShadow, {
degrees: rotationVal,
right: true
}), /*#__PURE__*/React.createElement(FrontShadow, shadowProps), /*#__PURE__*/React.createElement(PageShadow, _extends({}, shadowProps, {
containerSize: containerSize
}))), /*#__PURE__*/React.createElement(Animated.View, {
style: [styles.pageContainer, portraitFrontStyle]
}, renderPage && /*#__PURE__*/React.createElement(Animated.View, {
style: [frontPageStyle]
}, renderPage(page.left))));
};
export { BookPagePortrait };
const styles = StyleSheet.create({
container: {
flex: 1
},
pageContainer: {
height: '100%',
width: '100%',
position: 'absolute',
backfaceVisibility: 'hidden',
overflow: 'hidden',
backgroundColor: 'white'
}
});
//# sourceMappingURL=BookPagePortrait.js.map