I have a card game where the user can drag cards around a screen.
How can I detect if the cards have been dragged over other View components? My draggable card code is like this:
// A draggable card
// drag drop code example used: https://github.com/brentvatne/react-native-animated-demo-tinder
// panresponder docs: https://facebook.github.io/react-native/docs/panresponder.html
class Card extends React.Component {
componentWillMount() {
this.state = {
pan: new Animated.ValueXY(),
enter: new Animated.Value(1),
}
this._panResponder = PanResponder.create({
onMoveShouldSetResponderCapture: () => true,
onMoveShouldSetPanResponderCapture: () => true,
onPanResponderGrant: (e, gestureState) => {
Animated.spring(this.state.enter, {
toValue: .75,
}).start()
this.state.pan.setOffset({x: this.state.pan.x._value,
y: this.state.pan.y._value});
this.state.pan.setValue({x: 0, y: 0});
},
onPanResponderMove: Animated.event([
null, {dx: this.state.pan.x, dy: this.state.pan.y},
]),
onPanResponderRelease: (e, {vx, vy}) => {
// do stuff when card released
}
Animated.spring(this.state.enter, {
toValue: 1,
}).start()
this.state.pan.flattenOffset();
var velocity;
if (vx >= 0) {
velocity = clamp(vx, 3, 5);
} else if (vx < 0) {
velocity = clamp(vx * -1, 3, 5) * -1;
}
Animated.spring(this.state.pan, {
toValue: {x: 0, y: toValue},
friction: 4
}).start()
}
})
}
I don't know if this would be the best way to go about it, but I would just get the onLayout of the target Views to get the x,y,width, and height...
<View onLayout={({nativeEvent: {layout: {x,y,width,height}}) => { })} />
The coordinates covered by the container would simply be (x, x+width) and (y, y+height)
onPanResponderGrant get the location of the draggable:
onPanResponderGrant: (e) => {
this.originX = e.pageX - e.locationX;
this.originY = e.pageY - e.locationY;
}
onPanResponderMove you can do something like this:
onPanResponderMove: (e, gestureState) => {
const currentX0 = this.originX + gestureState.dx
const currentY0 = this.originY + gestureState.dy
}
The draggable now covers from currentX0 to currentX0 + width and from currentY0 to currentY0 + height...
You can use these values to check to see whether it overlaps with the target view (x, x+width) and (y, y+height)
Related
I am using this react code for getting a custom cursor following the main cursor. animation is done using gsap and lerp function. But the animation is not seamless and the chrome performance monitor shows the CPU usage is passing 100%. pls help me figure out this problem. I have referred to this video link for getting the cursor animation: https://www.youtube.com/watch?v=MEO6yQLAgKw&list=PLtSHrBhMos7hXeImRWnnC38mdYp_4c333&index=2&t=630s
import gsap from 'gsap';
import React, {Component} from 'react';
import './cursor.scss';
class Cursor extends Component{
constructor(props){
super(props);
this.state={
x : 0,
y : 0
};
this.cursor = React.createRef();
this.cursorConfigs = {
x: { previous: 0, current: 0, amt: 0.2 },
y: { previous: 0, current: 0, amt: 0.2 },
};
this.lerp = (a, b, n) => (1 - n) * a + n * b;
}
componentDidMount(){
window.addEventListener("mousemove", e=> {
this.setState({
x: e.pageX,
y:e.pageY
})
});
this.cursor.current.style.opacity = 0;
this.onMouseMoveEv = () => {
this.cursorConfigs.x.previous = this.cursorConfigs.x.current = this.state.x;
this.cursorConfigs.y.previous = this.cursorConfigs.y.current = this.state.y;
gsap.to(this.cursor.current,{
duration: 1,
ease: "Power4.easeOut",
opacity: 1,
});
window.removeEventListener("mousemove", this.onMouseMoveEv);
requestAnimationFrame(() =>this.render());
};
window.addEventListener("mousemove", this.onMouseMoveEv);
}
render(){
this.cursorConfigs.x.current = this.state.x;
this.cursorConfigs.y.current = this.state.y;
for (const Key in this.cursorConfigs){
this.cursorConfigs[Key].previous = this.lerp(
this.cursorConfigs[Key].previous,
this.cursorConfigs[Key].current,
this.cursorConfigs[Key].amt
);
}
console.log(this.cursorConfigs.x.previous, this.cursorConfigs.x.current)
var styles = {
transform:`translateX(${this.cursorConfigs.x.previous}px) translateY(${this.cursorConfigs.y.previous}px)`
}
requestAnimationFrame(() =>this.render());
return(
<div className="cursor" ref={this.cursor} style={styles}>
<div className="cursor-media">
</div>
</div>
)
}
}
export default Cursor;```
You actualy don't need to update on every mousemove. Consider debounce setState
example:
// delay in ms
function debounced(delay, fn) {
let timerId;
return function (...args) {
if (timerId) {
clearTimeout(timerId);
}
timerId = setTimeout(() => {
fn(...args);
timerId = null;
}, delay);
};
}
function handler(e) {
this.setState({
x: e.pageX,
y: e.pageY,
});
};
window.addEventListener("mousemove", debounced(200, handler));
I come to you because I have a problem with this library react-native-color-wheel, I would like to change the wheel so that it becomes a wheel of colors in kelvin. I've tried to tinker with several things but without any effect, I'm really struggling with polar calculations and all that goes with it if someone could point me to it.
Thanks to you guys
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Animated, Image, Dimensions, PanResponder, View } from 'react-native';
import colorsys from 'colorsys';
import styles from './styles';
export default class ColorWheelWhite extends Component {
static propTypes = {
toggleMeToforceUpdateInitialColor: PropTypes.number,
initialColor: PropTypes.string,
onColorChangeComplete: PropTypes.func,
thumbSize: PropTypes.number,
onColorChange: PropTypes.func,
thumbStyle: PropTypes.object,
prevState: PropTypes.string,
style: PropTypes.object
};
static defaultProps = {
thumbSize: 50,
initialColor: '#ffffff',
onColorChange: () => {}
};
static getDerivedStateFromProps(props, state) {
const { toggleMeToforceUpdateInitialColor } = props;
if (toggleMeToforceUpdateInitialColor !== state.toggleMeToforceUpdateInitialColor) {
return {
toggleMeToforceUpdateInitialColor
};
}
return null;
}
constructor(props) {
super(props);
const { initialColor, toggleMeToforceUpdateInitialColor = 0 } = props;
this.state = {
offset: { x: 100, y: 100 },
currentColor: initialColor,
pan: new Animated.ValueXY(),
toggleMeToforceUpdateInitialColor,
radius: 200,
panHandlerReady: true,
didUpdateThumb: false
};
}
componentDidMount = () => {
this._panResponder = PanResponder.create({
onStartShouldSetPanResponderCapture: () => true,
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponderCapture: () => true,
onPanResponderGrant: ({ nativeEvent }) => {
if (this.outBounds(nativeEvent)) return;
if (!this.state.didUpdateThumb) {
this.updateColorAndThumbPosition(nativeEvent);
}
},
onPanResponderMove: (event, gestureState) => {
if (this.outBounds(gestureState)) return;
if (!this.state.didUpdateThumb) {
const { nativeEvent } = event;
this.updateColorAndThumbPosition(nativeEvent);
}
this.resetPanHandler();
return Animated.event(
[
null,
{
dx: this.state.pan.x,
dy: this.state.pan.y
}
],
{ listener: this.updateColor }
)(event, gestureState);
},
onMoveShouldSetPanResponder: () => true,
onPanResponderRelease: ({ nativeEvent }) => {
this.setState({
panHandlerReady: true,
didUpdateThumb: false
});
this.state.pan.flattenOffset();
const { radius } = this.calcPolar(nativeEvent);
if (radius < 0.1) {
this.forceUpdate('#ffffff');
}
if (this.props.onColorChangeComplete) {
this.props.onColorChangeComplete(this.state.hsv);
}
}
});
};
componentDidUpdate(prevProps, prevState) {
const { initialColor } = this.props;
if (initialColor && this.state.toggleMeToforceUpdateInitialColor !== prevState.toggleMeToforceUpdateInitialColor) {
this.forceUpdate(initialColor);
}
}
updateColorAndThumbPosition(nativeEvent) {
this.updateColor({ nativeEvent });
this.state.pan.setValue({
x: -this.state.left + nativeEvent.pageX - this.props.thumbSize / 2,
y: -this.state.top + nativeEvent.pageY - this.props.thumbSize / 2
});
this.setState({
didUpdateThumb: true
});
}
onLayout = () => {
this.measureOffset();
};
measureOffset() {
/*
* const {x, y, width, height} = nativeEvent.layout
* onlayout values are different than measureInWindow
* x and y are the distances to its previous element
* but in measureInWindow they are relative to the window
*/
this.self.measureInWindow((x, y, width, height) => {
const window = Dimensions.get('window');
const absX = x % width;
const radius = Math.min(width, height) / 2;
const offset = {
x: absX + width / 2,
y: (y % window.height) + height / 2
};
this.setState({
offset,
radius,
height,
width,
top: y % window.height,
left: absX
});
this.forceUpdate(this.state.currentColor);
});
}
calcPolar(gestureState) {
const { pageX, pageY, moveX, moveY } = gestureState;
const [x, y] = [pageX || moveX, pageY || moveY];
const [dx, dy] = [x - this.state.offset.x, y - this.state.offset.y];
return {
deg: Math.atan2(dy, dx) * (-180 / Math.PI),
// pitagoras r^2 = x^2 + y^2 normalized
radius: Math.sqrt(dy * dy + dx * dx) / this.state.radius
};
}
outBounds(gestureState) {
const { radius } = this.calcPolar(gestureState);
return radius > 1;
}
resetPanHandler() {
if (!this.state.panHandlerReady) {
return;
}
this.setState({ panHandlerReady: false });
this.state.pan.setOffset({
x: this.state.pan.x._value,
y: this.state.pan.y._value
});
this.state.pan.setValue({ x: 0, y: 0 });
}
calcCartesian(deg, radius) {
const r = radius * this.state.radius; // was normalized
const rad = (Math.PI * deg) / 180;
const x = r * Math.cos(rad);
const y = r * Math.sin(rad);
return {
left: this.state.width / 2 + x,
top: this.state.height / 2 - y
};
}
updateColor = ({ nativeEvent }) => {
const { deg, radius } = this.calcPolar(nativeEvent);
console.log("deg");
console.log(deg);
console.log("radius");
console.log(radius);
const hsv = { h: deg, s: 10 * radius, v: 100 };
const currentColor = colorsys.hsv2Hex(hsv);
this.setState({ hsv, currentColor });
console.log("LE HSV");
console.log(this.state.hsv);
this.props.onColorChange(hsv);
};
forceUpdate = color => {
const { h, s, v } = colorsys.hex2Hsv(color);
const { left, top } = this.calcCartesian(h, s / 100);
this.setState({ currentColor: color });
this.props.onColorChange({ h, s, v });
this.state.pan.setValue({
x: left - this.props.thumbSize / 2,
y: top - this.props.thumbSize / 2
});
};
animatedUpdate = color => {
const { h, s, v } = colorsys.hex2Hsv(color);
const { left, top } = this.calcCartesian(h, s / 100);
this.setState({ currentColor: color });
this.props.onColorChange({ h, s, v });
Animated.spring(this.state.pan, {
toValue: {
x: left - this.props.thumbSize / 2,
y: top - this.props.thumbSize / 2
}
}).start();
};
setRef = ref => {
this.self = ref;
};
render() {
const { radius } = this.state;
const thumbStyle = [
styles().circle,
this.props.thumbStyle,
{
width: this.props.thumbSize,
height: this.props.thumbSize,
borderRadius: this.props.thumbSize / 2,
backgroundColor: this.state.currentColor,
opacity: this.state.offset.x === 0 ? 0 : 1
}
];
const panHandlers = (this._panResponder && this._panResponder.panHandlers) || {};
return (
<View ref={this.setRef} onLayout={this.onLayout} style={[styles().coverResponder, this.props.style]} pointerEvents={'box-none'}>
<Image
style={[
styles().img,
{
height: radius * 2,
width: radius * 2,
borderRadius: radius - this.props.thumbSize
}
]}
source={require('../../assets/color-wheel-white.png')}
{...panHandlers}
/>
<Animated.View style={[this.state.pan.getLayout(), thumbStyle]} pointerEvents={'box-none'} />
</View>
);
}
}
React Native documents give an example using PanResponder where a property is set using "._value".
What does "._value" do?
const pan = useRef(new Animated.ValueXY()).current;
const panResponder = useRef(
PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onPanResponderGrant: () => {
pan.setOffset({
x: pan.x._value, // What do you do?
y: pan.y._value // You too!
});
},
onPanResponderMove: Animated.event(
[
null,
{
dx: pan.x,
dy: pan.y
}
]
),
onPanResponderRelease: () => {
pan.flattenOffset();
}
})
).current;
First off, I'm new to asking stack overflow questions, so go easy on me guys.
I am trying to figure out how to detect collision of components; namely, a draggable component containing a button-circle component. Sadly, there appears to be zero information online regarding this. I'm thinking the draggable component may be able to handle some sort of radius tracking, which is computed when the user takes their finger off the screen, but hard tracking the radius would seem to be very resource intensive. I really need some ideas here guys. Relevant code is below.
Draggable.js
import React, { Component } from "react";
import {
PanResponder,
Animated,
} from "react-native";
import CircleButton from 'react-native-circle-button';
export default class Draggable extends Component {
constructor(props) {
super(props);
this.state = {
pan: new Animated.ValueXY(),
scale: new Animated.Value(1),
};
}
//Function to handle animations and button pressing of bubbles.
componentWillMount() {
let finalx = 0;
let finaly = 0;
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => false,
onMoveShouldSetPanResponder: (evt, gestureState) => {
return gestureState.dx <= finalx && gestureState.dy <= finaly;
},
onMoveShouldSetPanResponderCapture: (evt, gestureState) => {
return gestureState.dx <= finalx && gestureState.dy <= finaly;
},
onPanResponderGrant: (e, gestureState) => {
this.state.pan.setValue({x: 0, y: 0});
this.finalx = gestureState.dx;
this.finaly = gestureState.dy;
Animated.spring(
this.state.scale,
{ toValue: 1.1, friction: 3 }
).start();
},
onPanResponderMove: Animated.event([
null, {dx: this.state.pan.x, dy: this.state.pan.y},
]),
onPanResponderRelease: (e, {vx, vy}) => {
this.state.pan.flattenOffset();
Animated.spring(
this.state.pan,
{ toValue: {x:0, y:0}},
this.state.scale,
{ toValue: 1, friction: 3 }
).start();
}
});
}
render() {
let { pan } = this.state;
let rotate = '0deg';
let [translateX, translateY] = [pan.x, pan.y];
let moveableStyle = {transform: [{translateX}, {translateY}, {rotate}]};
const panStyle = {
transform: this.state.pan.getTranslateTransform()
}
return (
<Animated.View
{...this._panResponder.panHandlers}
style={[moveableStyle, panStyle]}>
<CircleButton />
</Animated.View>
);
}
}
Homepage.js
<View style={styles.middleContainer}>
{ this.state.bubbles.map((item, key)=>(
<Draggable key={key}> { item }</Draggable>)
)}
</View>
I've 3 Tabs, built using react-native-router-flux.
I'm using react-native-swiper-cards to create a Tinder Like Card Swiping UI on the center tab.
But if the card is been tried to swipe with a "complete or almost close to horizontal gesture", then instead of the card been dragged, the tabs are getting swiped. In all other cases cards are been dragged without the tab getting swiped.
How to disable the Tab Swiping when cards are getting dragged totally horizontally?
Tabs.js
<Scene
{...navbarPropsTabs}
key={'tabBar'} tabs={true}
swipeEnabled={true}
default="swipe">
<Scene
{...navbarPropsTabs}
key={'settings'}
component={SettingsScreen}
></Scene>
<Scene
{...navbarPropsTabs}
key={'swipe'}
component={SwipeScreen}
initial
></Scene>
<Scene
{...navbarPropsTabs}
key={'profile'}
component={ProfileScreen}>
</Scene>
Thats the code exact from the react-native-swipe-cards module.
this._panResponder = PanResponder.create({
onMoveShouldSetPanResponderCapture: (e, gestureState) => {
if (Math.abs(gestureState.dx) > this.minDeltaThreshold || Math.abs(gestureState.dy) > this.minDeltaThreshold) {
this.props.onDragStart();
return true;
}
return false;
},
onPanResponderGrant: (e, gestureState) => {
this.state.pan.setOffset({ x: this.state.pan.x._value, y: this.state.pan.y._value });
this.state.pan.setValue({ x: 0, y: 0 });
},
onPanResponderTerminationRequest: (evt, gestureState) => this.props.allowGestureTermination,
onPanResponderMove: Animated.event([
null, { dx: this.state.pan.x, dy: this.props.dragY ? this.state.pan.y : 0 },
]),
onPanResponderRelease: (e, {vx, vy, dx, dy}) => {
this.props.onDragRelease()
this.state.pan.flattenOffset();
let velocity;
if (Math.abs(dx) <= 5 && Math.abs(dy) <= 5) //meaning the gesture did not cover any distance
{
this.props.onClickHandler(this.state.card)
}
if (vx > 0) {
velocity = clamp(vx, 3, 5);
} else if (vx < 0) {
velocity = clamp(vx * -1, 3, 5) * -1;
} else {
velocity = dx < 0 ? -3 : 3;
}
const hasSwipedHorizontally = Math.abs(this.state.pan.x._value) > SWIPE_THRESHOLD
const hasSwipedVertically = Math.abs(this.state.pan.y._value) > SWIPE_THRESHOLD
if (hasSwipedHorizontally || (hasSwipedVertically && this.props.hasSwipeUpAction)) {
let cancelled = false;
const hasMovedRight = hasSwipedHorizontally && this.state.pan.x._value > 0
const hasMovedLeft = hasSwipedHorizontally && this.state.pan.x._value < 0
const hasMovedUp = hasSwipedVertically && this.state.pan.y._value < 0
if (hasMovedRight) {
cancelled = this.props.onSwipeRight(this.state.card);
} else if (hasMovedLeft) {
cancelled = this.props.onSwipeLeft(this.state.card);
} else if (hasMovedUp && this.props.hasSwipeUpAction) {
cancelled = this.props.onSwipeUp(this.state.card);
} else {
cancelled = true
}
//Yup or nope was cancelled, return the card to normal.
if (cancelled) {
this._resetPan();
return;
};
this.props.onCardRemoved(currentIndex[this.guid]);
if (this.props.smoothTransition) {
this._advanceState();
} else {
this.cardAnimation = Animated.decay(this.state.pan, {
velocity: { x: velocity, y: vy },
deceleration: 0.98
});
this.cardAnimation.start(status => {
if (status.finished) this._advanceState();
else this._resetState();
this.cardAnimation = null;
}
);
}
} else {
this._resetPan();
}
}
});