I have the following component:
class Ball extends React.Component {
constructor(props) {
super(props);
this.state = { frame: 0, position: this.positionBall(0) };
this.nextFrame();
}
nextFrame() {
this.setState(prevState => ({
frame: prevState.frame + 1,
position: this.positionBall(prevState.frame + 1)
}));
requestAnimationFrame(() => this.nextFrame());
}
render() {
return (
<svg style={{ width: "100%", height: "100%" }}>
<circle
cx={this.state.position}
cy={this.height() / 2}
r={this.radius()}
/>
</svg>
);
}
height() {
return document.documentElement.clientHeight;
}
width() {
return document.documentElement.clientWidth;
}
radius() {
return this.height() / 10;
}
positionBall(frame) {
const maximumPosition = this.width() - 2 * this.radius();
const maximumFrame = 120;
const slope = maximumPosition / maximumFrame;
const position = Math.round(
maximumPosition +
this.radius() -
1 * Math.abs(frame % (maximumFrame * 2) - maximumFrame) * slope
);
return position;
}
}
It is a very simple animation, but it doesn't run entire smooth all the time, especially on wide screens. How can I improve this code?
See this codesandbox:
https://codesandbox.io/s/22kzjy21n
Related
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>
);
}
}
My issue if the following:
The duration of the video is 6.357 seconds. The progress.currentTime only goes to 6.154 and stops.
Since that this.state.progress = 0.97 (not 1.0). As an outcome my ProgressBar doesn't go to the end. It stops on 0.97 position and onProgressPress() doesn't work.
Could someone please assist?
Here is my code:
export default class VideoComp extends Component {
state = {
paused: false,
progress: 0,
duration: 0
}
onProgressPress = (e) => {
const position = e.nativeEvent.locationX;
const progress = (position / 250) * this.state.duration;
this.player.seek(progress);
}
onMainButtonPress = () => {
if(this.state.progress >= 1) {
this.player.seek(0);
};
this.setState(state => {
return {
paused: !state.paused
}
})
}
handleEnd = () => {
this.setState({
paused: true
})
}
handleProgress = (progress) => {
this.setState({
progress: progress.currentTime / this.state.duration
});
}
handleLoad = (meta) => {
this.setState({
duration: meta.duration
})
}
render() {
const { width } = Dimensions.get('window');
const height = width * 0.5625;
return(
<View style = {styles.videoWrapper}>
<Video
source = {{uri: this.props.videoURL}}
ref = {ref => this.player = ref}
style = {{width: '100%', height}}
paused = {this.state.paused}
resizeMode = 'contain'
onLoad = {this.handleLoad}
onProgress = {this.handleProgress}
onEnd = {this.handleEnd}
/>
<View style = {styles.controls}>
<TouchableWithoutFeedback onPress = {this.onMainButtonPress}>
<IconSimpleLine name = {!this.state.paused ? 'control-pause' : 'control-play'} color = {text} size = {20}/>
</TouchableWithoutFeedback>
<TouchableWithoutFeedback onPress = {this.onProgressPress}>
<View>
<ProgressBar
progress = {this.state.progress}
width = {250}
height = {5}
color = {text}
borderColor = {text}
unfilledColor = 'rgba(255, 255, 255, 0.3)'
/>
</View>
</TouchableWithoutFeedback>
<Text style = {styles.duration}>
{secondsToTime(Math.floor(this.state.progress * this.state.duration))}
</Text>
</View>
</View>
)
}
}
UPDATE
I tried the next:
handleProgress = (progress) => {
this.setState({
progress: Math.floor(progress.currentTime) / this.state.duration
});
}
handleLoad = (meta) => {
this.setState({
duration: Math.floor(meta.duration)
})
}
The ProgressBar line now goes to the very end but it moves by a second. I mean, it moves a bit, stops on one second, moves a bit farther, stops on one second and so on. It doesn't move smoothly (each millisecond).
But it's not the correct solution.
the Video component has an onEnd prop. use that to update your state to 100%
Try to user parseFloat() for calculation purpose.
in your question, I don't know which value is coming in float or in a fraction number but just for example use as this.
this.setState({
progress: parseFloat(progress.currentTime / this.state.duration)
});
and
const progress = parseFloat(position / 250)) * this.state.duration;
Hope it will help you.
I'm trying to render a pie chart through an svg with a gap/line between the slices, but I'm not currently getting the right results. This is how it should look like.
This is how I've done the component, this is with reference to this medium post.
import React from 'react';
import { View, ViewPropTypes } from 'react-native';
import PropTypes from 'prop-types';
import Svg, { Path, G } from 'react-native-svg';
import { styleHelper } from '../../../helpers';
const maxPercentage = 100;
const generateCoordinates = (percent) => {
const a = (percent * 2 * Math.PI) / maxPercentage;
const x = Math.cos(a);
const y = Math.sin(a);
return [x, y];
};
const PieChart = ({ style, data, size }) => {
const radius = size / 2;
const viewBox = `-${radius} -${radius} ${size} ${size}`;
let cumulativePercent = 0;
return (
<View style={[style, { width: size, height: size }]}>
<Svg
width={size}
height={size}
viewBox={viewBox}
style={{ transform: [{ rotate: '-90deg' }] }}
>
{data.map((slice) => {
const largeArcFlag = slice.percent > (maxPercentage / 2) ? 1 : 0;
const xAxisRotation = 0;
const sweepFlag = 1;
const scaleValue = slice.percentScale / maxPercentage;
const [startX, startY] = generateCoordinates(cumulativePercent);
const [endX, endY] = generateCoordinates(cumulativePercent += slice.percent);
// Building the SVG
const m = `M ${startX * radius} ${startY * radius}`;
const a = (
`A ${radius} ${radius} ${xAxisRotation} ${largeArcFlag} ${sweepFlag} ${endX * radius} ${endY * radius}`
);
const l = 'L 0 0';
return (
<G
key={slice.id}
transform={`scale(${scaleValue})`}
>
<Path
d={`${m} ${a} ${l}`}
fill={styleHelper.renderStyle(slice.color)}
/>
</G>
);
})}
</Svg>
</View>
);
};
PieChart.propTypes = {
style: ViewPropTypes.style,
data: PropTypes.arrayOf(PropTypes.shape({
location: PropTypes.string,
leads: PropTypes.number,
percent: PropTypes.number,
percentScale: PropTypes.number,
color: PropTypes.string,
})).isRequired,
size: PropTypes.number.isRequired,
};
PieChart.defaultProps = {
style: {},
};
export default PieChart;
This is currently rendering a circular pie chart with slices scaled with respect to the biggest value. There is no line/gap currently separating the slices.
Any comments or suggestions would really be valuable. Thanks!
I have two pens, and I'm trying to use a React component I defined in one pen inside another, but I'm not clear on how Codepen actually handles React imports between pens. I went to the destination pen and added the source pen's address to the Javascript references, but I don't know how to proceed from there. I can get this to work in a local node project using traditional export, but the Codepen element is giving me trouble. Here's the code:
SOURCE (https://codepen.io/ejpg/pen/LmOVoR):
export default class Wheel extends React.Component // Export default causes error
{
constructor(props){
super(props);
this.state = {
spin : false,
value: 0
};
this.spin = this.spin.bind(this);
}
spin(e){
var val = this.state.value + 720 + (Math.floor(Math.random() * 24) * 15);
console.log((this.state.value % 360) / 15);
e.target.style.webkitTransform = 'rotate(' + -val + 'deg)';
e.target.style.webkitTransition = '-webkit-transform 4s ease-out';
this.setState({value: val});
}
render(){
const wheelVals = [800, "BANKRUPT", 200, 300, 350, 250, 400, 300, 200, 250, 500, 350, 250,
"BANKRUPT", 200, 300, 400, 250, 600, "LOSE A TURN", 200, 300, 250, 200];
return (<div><img width="400" height="400" src="https://orig00.deviantart.net/0a38/f/2010/242/f/6/singapore_wheel_of_fortune_by_wheelgenius-d2xmb9v.jpg" onClick={(e) => this.spin(e)}/><br/><br/>{wheelVals[(this.state.value % 360) / 15]}
</div>);
}
}
DESTINATION (https://codepen.io/ejpg/pen/bMgWpN):
let { Grid, Row, Col, ButtonToolbar, Button } = ReactBootstrap;
// How do I import the class?
class CustomButton extends React.Component {
onHandleClick = () => {
this.props.onClick();
};
render(){
return <Button bsStyle={this.props.bsStyle} onClick={this.onHandleClick}><strong>{this.props.text}</strong></Button>;
}
}
class Letter extends React.Component {
onHandleClick = () => {
this.props.onClick(this.props.letter);
};
render () {
const style = { border: '1px solid black',
display: 'inline-block',
fontSize: '3.5vw',
width: '4vw',
height: '10vh',
textAlign: 'center',
whiteSpace: 'no-wrap',
overflow: 'hidden'};
if (this.props.letter === ' ') style.border = '';
return (
<div
style={style}
key={this.props.key}
onClick={this.onHandleClick} // Have to pass onClick to div
>
{this.props.letter}
</div>
);
}
}
class MyComponent extends React.Component {
constructor(props) {
super(props);
var blanks = '';
for (var i = 0; i < this.props.answer.length; ++i)
{
this.props.answer[i] === ' ' ?
blanks += ' ': blanks += '-';
}
this.state = {
phrase: blanks,
alpha: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
bonus: false,
revealed: false
};
this.callLetter = this.callLetter.bind(this);
this.bonusRound = this.bonusRound.bind(this);
this.complete = this.complete.bind(this);
}
replaceAt(str, index, replacement) {
return str.substr(0, index) + replacement + str.substr(index + replacement.length);
}
complete(){
if (this.state.revealed === false)
{
this.setState({phrase: this.props.answer, revealed: true});
}
}
checkForLetter(letter, phr)
{
this.setState((prevState, props) => {
var prephrase = prevState.phrase;
var index = phr.indexOf(letter);
while( index !== -1)
{
prephrase = this.replaceAt(prephrase, index, letter);
index = phr.indexOf(letter, index + 1);
}
return ({phrase: prephrase});
});
}
callLetter(letter) {
this.setState((prevState, props) => {
var alphaclone = prevState.alpha;
var letterindex = alphaclone.indexOf(letter);
alphaclone = alphaclone.slice(0, letterindex) + alphaclone.slice(letterindex + 1);
return ({alpha: alphaclone});
});
this.checkForLetter(letter, this.props.answer);
}
bonusRound(){
if (this.state.bonus === false)
{
this.callLetter('R');
this.callLetter('S');
this.callLetter('T');
this.callLetter('L');
this.callLetter('N');
this.callLetter('E');
this.setState({bonus: true});
}
}
render() {
return (
<Grid>
<Row className="show-grid" >
{
this.state.phrase.split(' ').map((item, j) =>
(
<div style = {{display:'inline-block'}}>
<Letter letter = {' '}/>
{item.split('').map((item, i) =>
(
<Letter letter= {item}/>
)) }
</div>
))
}
</Row>
<Row className="show-grid" style={{margin: '3vh'}}>
{
this.state.alpha.split('').map((item, i) =>
(
<Letter letter={item} key={i} onClick={this.callLetter}/>
))
}
</Row>
<Row className="show-grid" style={{margin: '3vh'}}>
<ButtonToolbar>
<CustomButton bsStyle = {"primary"} text= {"BONUS ROUND"} onClick = {this.bonusRound}/>
<CustomButton bsStyle = {"danger"} text= {"REVEAL ALL"} onClick = {this.complete}/>
</ButtonToolbar>
</Row>
</Grid>
);
}
}
ReactDOM.render(
<MyComponent answer='A VERY VERY EXCESSIVELY LONG TEST'/>,
document.getElementsByClassName('container-fluid')[0]
);
Any help is greatly appreciated.
EDIT: I can't believe I actually have to explicitly state that I don't want to copy and paste it.
For that you have to make your pen containing the component as a module. You can do this by going to Settings > Javascript and checking the Add type="module" checkbox.
Then you can import the component in another pen using the URL of your pen:
import MyComponent from 'https://codepen.io/user/pen/xyz.js';
The entire doc regarding this may be found here: https://blog.codepen.io/2017/12/26/adding-typemodule-scripts-pens/.
Hope this helps :)
I'm trying to create a React Component that zooms an image and the zoomed image follows the mouse.
I've created a pen to show what I've done so far: React Zoom
And here is the code:
class ProductGallery extends React.Component {
constructor (props) {
super(props)
this.state = {
imgZoomed: false,
mouseX: undefined,
mouseY: undefined
}
this.zoomImage = this.zoomImage.bind(this)
this.unZoomImage = this.unZoomImage.bind(this)
this.moveMouseImg = this.moveMouseImg.bind(this)
}
zoomImage () {
this.setState({
imgZoomed: true
})
}
unZoomImage () {
this.setState({
imgZoomed: false
})
}
moveMouseImg (e) {
const {
top: offsetTop,
left: offsetLeft
} = e.target.getBoundingClientRect()
const x = ((e.pageX - offsetLeft) / e.target.width) * 100
const y = ((e.pageY - offsetTop) / e.target.height) * 100
this.setState({
mouseX: x,
mouseY: y
})
}
render () {
const transform = {
transformOrigin: `${this.state.mouseX}% ${this.state.mouseY}%`
}
const classes = Object.assign({}, transform, {
transform: this.state.imgZoomed ? 'scale(2.0)' : 'scale(1.0)',
maxHeight: '615px',
maxWidth: '100%',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
backgroundSize: 'cover',
transition: 'transform .1s ease-out'
})
const divStyles = {
height: '615px',
width: '615px',
float: 'left',
marginLeft: '10px',
overflow: 'hidden',
borderStyle: 'solid'
}
return (
<div style={divStyles}>
<img
src="http://www.planwallpaper.com/static/images/maxresdefault_8yZPhSS.jpg"
style={classes}
alt="dmsak"
onMouseOver={this.zoomImage}
onMouseOut={this.unZoomImage}
onMouseMove={this.moveMouseImg}
/>
</div>
)
}
}
React.render(<ProductGallery />, document.getElementById('app'));
The problem is that when I move the mouse, the component just starts to "shake"! I am using a scale of 2.0, but if I change it to 1.5 (transform: scale(1.5)), the problem only happens in the edges of the image.
How can I solve it?