I've made this effect with React and Tailwind:
GIF with the effect
As you can see, so far, so good. However, this effect just works when:
I scroll on the browser
I click on the browser
I open the Chrome tools inspector
This is what happens when I do not do something of the above:
GIF with the effect but not working as expected
As you can see, that's not the effect I'm looking for.
So, apparently, there is a React render problem ocurring right here, and I'm stucked.
Inside, this works as follows:
const HomeHeader = () => {
// This are imported images
const IMAGES = [insta, twitter, netflix, mix];
// This is an array that maps inside the component and renders the images as JSX
const [images, setImages] = useState([]);
useEffect(() => {
let counter = 0;
// An interval that creates a new element in the array "images" every 50 miliseconds
const interval = setInterval(() => {
setImages(prevState => [
...prevState,
{
id: counter++,
duration: getRandom(5, 9) * 1000,
image: IMAGES[getRandom(0, IMAGES.length)],
height: getRandom(1, 6),
top: getRandom(0, window.innerHeight - window.innerHeight / 4),
right: getRandom(0, window.innerWidth)
}
]);
}, 50);
return () => clearInterval(interval);
}, [])
// Every time the state variable "images" is changed, add the cool animation to the last image and, when it ends, delete the image from the HTML
useEffect(() => {
if(images.length > 0) {
let image = images[images.length - 1];
let element = document.getElementById(image.id);
element.classList.add('opacity-0');
element.classList.add('scale-125');
setTimeout(() => {
element.parentNode.removeChild(element);
}, image.duration);
}
}, [images])
return (
<div id="header" className="relative h-full bg-gray-900 flex justify-center items-center px-16 overflow-hidden">
<h1 className="bg-black text-gray-200 font-bold text-5xl sm:text-8xl md:text-9xl text-center z-10"></h1>
{
images.map((element, index) => {
return <img key={index} id={element.id} src={element.image} style={{top: element.top, right: element.right}} className={"imageRRSS absolute filter blur-sm w-auto h-" + element.height + "/6 w-auto transform transition-transform-opacity duration-" + element.duration}/>
})
}
</div>
)
}
What it does is to add every 50 miliseconds a new object to the state array images that contains the properties of the image to add.
After each addition, as it is a state variable, React re-renders the mapping of the component inside of the return:
{
images.map((element, index) => {
return <img key={index} id={element.id} src={element.image} style={{top: element.top, right: element.right}} className={"imageRRSS absolute filter blur-sm w-auto h-" + element.height + "/6 w-auto transform transition-transform-opacity duration-" + element.duration}/>
})
}
And also it goes to the useEffect and adds the cool animation and, when the animations ends, it deletes the tag from the HTML:
useEffect(() => {
if(images.length > 0) {
let image = images[images.length - 1];
let element = document.getElementById(image.id);
element.classList.add('opacity-0');
element.classList.add('scale-125');
setTimeout(() => {
element.parentNode.removeChild(element);
}, image.duration);
}
}, [images])
However, the render just occurs when I interact with the browser.
This is how I understand what it's happening. However, something is clearly wrong as it's not working as expected.
Anybody knows what it's happening?
Thanks!
Ok, solved. The problem was with React applying tailwind effects instantly in the element. I just added the onLoad event to the component instead of ussing useEffect and it works perfectly.
I am a newbie and I need help, please. I have been looking for a solution for long time, but cannot go further. I have a chess table with pieces without repeating codes, but I am stuck at a part when the pieces should move to positions I define. Its a chess analysis and all position is defined by me. This code below works, but it is repetitive.
I want the code not repetitive
if I click any button (except the first move), then all the previous button click should fire as well at once. So one move follow another.
if the above things would work, I also want to remove those chess pieces that was defined after a certain button. So if I am at the position of #move4 and I click the button #move3, then I want to move the chess piece back to position #move3
Thank you very much for help and for any advice.
HTML code:
<button id="move1" class="move">d4</button>
<button id="move2" class="move">Nf6</button>
<button id="move3" class="move">c4</button>
<button id="move4" class="move">g6</button>
jQuery code:
$('#move1').on('click', function () {
pieces[19].y = 4;
drawBoard();
drawAllPieces();
});
$('#move2').on('click', function () {
pieces[11].y = 2;
pieces[11].x = 5;
$("#move1").click();
drawBoard();
drawAllPieces();
});
$('#move3').on('click', function () {
pieces[18].y = 4;
$("#move1").click();
$("#move2").click();
drawBoard();
drawAllPieces();
});
$('#move4').on('click', function () {
pieces[6].y = 2;
$("#move1").click();
$("#move2").click();
$("#move3").click();
drawBoard();
drawAllPieces();
});
You can use a loop to make it dynamic -
for(const i of [1,2,3,4]) {
$(`#move${i}`').on('click', function () {
pieces[19].y = 4;
drawBoard();
drawAllPieces();
});
I would store the moves in an array then you only need a single handler for the click.
<button data-move="0">d4</button>
<button data-move="1">Nf6</button>
<button data-move="2">c4</button>
<button data-move="3">g6</button>
var moves = [
{ piece: 19, y: 4 },
{ piece: 11, y: 2, x: 5 },
{ piece: 18, y: 4 },
{ piece: 6, y: 2 },
];
$('button').on('click', function () {
var index = +$(this).attr('data-move');
for (var i = 0; i <= index; i++) {
var data = moves[i];
if (!isNaN(data.y)) // check if the data has y property
pieces[data.piece] = data.y;
if (!isNaN(data.x)) // check if the data has x property
pieces[data.piece] = data.x;
}
drawBoard();
drawAllPieces();
});
I'm using react-three-renderer (npm, github) for building a scene with three.js.
I'm having a problem that I've boiled down to an MVCE. Refs aren't updating in the sequence I expect them to. First, here's the main code to look at:
var React = require('react');
var React3 = require('react-three-renderer');
var THREE = require('three');
var ReactDOM = require('react-dom');
class Simple extends React.Component {
constructor(props, context) {
super(props, context);
// construct the position vector here, because if we use 'new' within render,
// React will think that things have changed when they have not.
this.cameraPosition = new THREE.Vector3(0, 0, 5);
this.state = {
shape: 'box'
};
this.toggleShape = this.toggleShape.bind(this);
}
toggleShape() {
if(this.state.shape === 'box') {
this.setState({ shape: 'circle' });
} else {
this.setState({ shape: 'box' });
}
}
renderShape() {
if(this.state.shape === 'box') {
return <mesh>
<boxGeometry
width={1}
height={1}
depth={1}
name='box'
ref={
(shape) => {
this.shape = shape;
console.log('box ref ' + shape);
}
}
/>
<meshBasicMaterial
color={0x00ff00}
/>
</mesh>;
} else {
return <mesh>
<circleGeometry
radius={2}
segments={50}
name='circle'
ref={
(shape) => {
this.shape = shape;
console.log('circle ref ' + shape);
}
}
/>
<meshBasicMaterial
color={0x0000ff}
/>
</mesh>
}
}
componentDidUpdate() {
console.log('componentDidUpdate: the active shape is ' + this.shape.name);
}
render() {
const width = window.innerWidth; // canvas width
const height = window.innerHeight; // canvas height
var position = new THREE.Vector3(0, 0, 10);
var scale = new THREE.Vector3(100,50,1);
var shape = this.renderShape();
return (<div>
<button onClick={this.toggleShape}>Toggle Shape</button>
<React3
mainCamera="camera"
width={width}
height={height}
onAnimate={this._onAnimate}>
<scene>
<perspectiveCamera
name="camera"
fov={75}
aspect={width / height}
near={0.1}
far={1000}
position={this.cameraPosition}/>
{shape}
</scene>
</React3>
</div>);
}
}
ReactDOM.render(<Simple/>, document.querySelector('.root-anchor'));
This renders a basic scene with a green box, a fork of the example on react-three-renderer's github landing page. The button on the top left toggles the shape in the scene to be a blue circle, and if clicked again, back to the green box. I'm doing some logging in the ref callbacks and in componentDidUpdate. Here's where the core of the problem I'm encountering occurs. After clicking the toggle button for the first time, I expect the ref for the shape to be pointing to the circle. But as you can see from the logging, in componentDidUpdate the ref is still pointing to the box:
componentDidUpdate: the active shape is box
Logging in lines after that reveals the ref callbacks are hit
box ref null [React calls null on the old ref to prevent memory leaks]
circle ref [object Object]
You can drop breakpoints in to verify and to inspect. I would expect these two things to happen before we enter componentDidUpdate, but as you can see, it's happening in reverse. Why is this? Is there an underlying issue in react-three-renderer (if so, can you diagnose it?), or am I misunderstanding React refs?
The MVCE is available in this github repository. Download it, run npm install, and open _dev/public/home.html.
Thanks in advance.
I checked the source in react-three-renderer. In lib/React3.jsx, there is a two phased render.
componentDidMount() {
this.react3Renderer = new React3Renderer();
this._render();
}
componentDidUpdate() {
this._render();
}
The _render method is the one that seems to loads the children - the mesh objects within Three.
_render() {
const canvas = this._canvas;
const propsToClone = { ...this.props };
delete propsToClone.canvasStyle;
this.react3Renderer.render(
<react3
{...propsToClone}
onRecreateCanvas={this._onRecreateCanvas}
>
{this.props.children}
</react3>, canvas);
}
The render method draws the canvas and does not populate the children or invoke Three.
render() {
const {
canvasKey,
} = this.state;
return (<canvas
ref={this._canvasRef}
key={canvasKey}
width={this.props.width}
height={this.props.height}
style={{
...this.props.canvasStyle,
width: this.props.width,
height: this.props.height,
}}
/>);
}
Summarizing, this is the sequence:
App Component render is called.
This renders react-three-renderer which draws out a canvas.
componentDidUpdate of App component is called.
componentDidUpdate of react-three-renderer is called.
This calls the _render method.
The _render method updates the canvas by passing props.children (mesh objects) to the Three library.
When the mesh objects are mounted, the respective refs are invoked.
This explains what you are observing in the console statements.
I tried asking this question before but the way I asked it was so confusing that I didn't get any help. I originally thought it was React to blame for my touchmove events to ceasefire when updating subcomponents. I now am pretty sure it is the Chartist.js library, or possibly how I'm wrapping chartist into a react component, that is stopping the action.
Instead of rambling on about my question I've created two JSfiddles. One that shows you can create a React slider that updates it's values continuously, regardless of being called from mousemove or touchmove.
http://jsfiddle.net/higginsrob/uf6keps2/
// please follow the link for full example
The Second fiddle implements my react wrapper for chartist, and a simplified example of how I'm using it. When you click/drag on the chart it will select the data point at the current x value. This is working fine with a mouse, but trying it on mobile touch devices (or chrome's mobile emulator) it will only fire a few times, and only update the chart once.
http://jsfiddle.net/higginsrob/Lpcg1c6w/
// please follow the link for full example
Any help is appreciated!
Ok, so you need to put a transparent div in front of the chartist chart that captures the mousedown/touchstart, mousemove/touchmove, and mouseup/touchend events.
working example:
http://jsfiddle.net/higginsrob/jwhbzgrb/
// updated event functions:
onTouchStart: function (evt) {
evt.preventDefault();
this.is_touch = (evt.touches);
var node = evt.currentTarget.previousSibling;
var grid = node.querySelector('.ct-grids');
var bbox = grid.getBBox();
this.columnwidth = bbox.width / this.props.data.length;
this.offset = this.getScrollLeftOffset(node) + bbox.x + (this.columnwidth / 2);
this.istouching = true;
this.onTouchMove(evt);
}
onTouchMove: function (evt) {
if(this.istouching){
var x;
if (this.is_touch) {
if(evt.touches && evt.touches[0]){
x = evt.touches[0].clientX - this.offset;
}
} else {
x = evt.clientX - this.offset;
}
this.setState({
index: Math.round(x / this.columnwidth)
});
}
}
onTouchEnd: function(evt){
this.istouching = false;
}
// updated render function:
render: function () {
return React.DOM.div(
{
style: {
position: "relative"
}
},
ReactChartist({ ... your chartist chart here .... }),
// this div sits in front of the chart and captures events
React.DOM.div({
onMouseDown: this.onTouchStart,
onTouchStart: this.onTouchStart,
onMouseMove: this.onTouchMove,
onTouchMove: this.onTouchMove,
onMouseUp: this.onTouchEnd,
onTouchEnd: this.onTouchEnd,
style: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0
}
})
);
}
I've seen this hack for native apps to auto scroll the window, but wondering best way to do it in React Native... When a <TextInput> field gets focus and is positioned low in the view, the keyboard will cover up the text field.
You can see this issue in example UIExplorer's TextInputExample.js view.
Does anyone have a good solution?
2017 Answer
The KeyboardAvoidingView is probably the best way to go now. Check out the docs here. It is really simple compared to Keyboard module which gives Developer more control to perform animations. Spencer Carli demonstrated all the possible ways on his medium blog.
2015 Answer
The correct way to do this in react-native does not require external libraries, takes advantage of native code, and includes animations.
First define a function that will handle the onFocus event for each TextInput (or any other component you would like to scroll to):
// Scroll a component into view. Just pass the component ref string.
inputFocused (refName) {
setTimeout(() => {
let scrollResponder = this.refs.scrollView.getScrollResponder();
scrollResponder.scrollResponderScrollNativeHandleToKeyboard(
React.findNodeHandle(this.refs[refName]),
110, //additionalOffset
true
);
}, 50);
}
Then, in your render function:
render () {
return (
<ScrollView ref='scrollView'>
<TextInput ref='username'
onFocus={this.inputFocused.bind(this, 'username')}
</ScrollView>
)
}
This uses the RCTDeviceEventEmitter for keyboard events and sizing, measures the position of the component using RCTUIManager.measureLayout, and calculates the exact scroll movement required in scrollResponderInputMeasureAndScrollToKeyboard.
You may want to play around with the additionalOffset parameter, to fit the needs of your specific UI design.
Facebook open sourced KeyboardAvoidingView in react native 0.29 to solve this problem. Documentation and usage example can be found here.
We combined some of the code form react-native-keyboard-spacer and the code from #Sherlock to create a KeyboardHandler component that can be wrapped around any View with TextInput elements. Works like a charm! :-)
/**
* Handle resizing enclosed View and scrolling to input
* Usage:
* <KeyboardHandler ref='kh' offset={50}>
* <View>
* ...
* <TextInput ref='username'
* onFocus={()=>this.refs.kh.inputFocused(this,'username')}/>
* ...
* </View>
* </KeyboardHandler>
*
* offset is optional and defaults to 34
* Any other specified props will be passed on to ScrollView
*/
'use strict';
var React=require('react-native');
var {
ScrollView,
View,
DeviceEventEmitter,
}=React;
var myprops={
offset:34,
}
var KeyboardHandler=React.createClass({
propTypes:{
offset: React.PropTypes.number,
},
getDefaultProps(){
return myprops;
},
getInitialState(){
DeviceEventEmitter.addListener('keyboardDidShow',(frames)=>{
if (!frames.endCoordinates) return;
this.setState({keyboardSpace: frames.endCoordinates.height});
});
DeviceEventEmitter.addListener('keyboardWillHide',(frames)=>{
this.setState({keyboardSpace:0});
});
this.scrollviewProps={
automaticallyAdjustContentInsets:true,
scrollEventThrottle:200,
};
// pass on any props we don't own to ScrollView
Object.keys(this.props).filter((n)=>{return n!='children'})
.forEach((e)=>{if(!myprops[e])this.scrollviewProps[e]=this.props[e]});
return {
keyboardSpace:0,
};
},
render(){
return (
<ScrollView ref='scrollView' {...this.scrollviewProps}>
{this.props.children}
<View style={{height:this.state.keyboardSpace}}></View>
</ScrollView>
);
},
inputFocused(_this,refName){
setTimeout(()=>{
let scrollResponder=this.refs.scrollView.getScrollResponder();
scrollResponder.scrollResponderScrollNativeHandleToKeyboard(
React.findNodeHandle(_this.refs[refName]),
this.props.offset, //additionalOffset
true
);
}, 50);
}
}) // KeyboardHandler
module.exports=KeyboardHandler;
First you need to install react-native-keyboardevents.
In XCode, in the project navigator, right click Libraries ➜ Add
Files to [your project's name] Go to node_modules ➜
react-native-keyboardevents and add the .xcodeproj file
In XCode, in the
project navigator, select your project. Add the lib*.a from the keyboardevents
project to your project's Build Phases ➜ Link Binary With Libraries Click
.xcodeproj file you added before in the project navigator and go the Build
Settings tab. Make sure 'All' is toggled on (instead of 'Basic').
Look for Header Search Paths and make sure it contains both
$(SRCROOT)/../react-native/React and $(SRCROOT)/../../React - mark
both as recursive.
Run your project (Cmd+R)
Then back in javascript land:
You need to import the react-native-keyboardevents.
var KeyboardEvents = require('react-native-keyboardevents');
var KeyboardEventEmitter = KeyboardEvents.Emitter;
Then in your view, add some state for the keyboard space and update from listening to the keyboard events.
getInitialState: function() {
KeyboardEventEmitter.on(KeyboardEvents.KeyboardDidShowEvent, (frames) => {
this.setState({keyboardSpace: frames.end.height});
});
KeyboardEventEmitter.on(KeyboardEvents.KeyboardWillHideEvent, (frames) => {
this.setState({keyboardSpace: 0});
});
return {
keyboardSpace: 0,
};
},
Finally, add a spacer to your render function beneath everything so when it increases size it bumps your stuff up.
<View style={{height: this.state.keyboardSpace}}></View>
It is also possible to use the animation api, but for simplicity's sake we just adjust after the animation.
react-native-keyboard-aware-scroll-view solved the problem for me.
react-native-keyboard-aware-scroll-view on GitHub
Try this:
import React, {
DeviceEventEmitter,
Dimensions
} from 'react-native';
...
getInitialState: function() {
return {
visibleHeight: Dimensions.get('window').height
}
},
...
componentDidMount: function() {
let self = this;
DeviceEventEmitter.addListener('keyboardWillShow', function(e: Event) {
self.keyboardWillShow(e);
});
DeviceEventEmitter.addListener('keyboardWillHide', function(e: Event) {
self.keyboardWillHide(e);
});
}
...
keyboardWillShow (e) {
let newSize = Dimensions.get('window').height - e.endCoordinates.height;
this.setState({visibleHeight: newSize});
},
keyboardWillHide (e) {
this.setState({visibleHeight: Dimensions.get('window').height});
},
...
render: function() {
return (<View style={{height: this.state.visibleHeight}}>your view code here...</View>);
}
...
It worked for me. The view basically shrinks when the keyboard is displayed, and grows back again when its hidden.
Just wanted to mention, now there is a KeyboardAvoidingView in RN. Just import it and use it as any other module in RN.
Here is the link to the commit on RN:
https://github.com/facebook/react-native/commit/8b78846a9501ef9c5ce9d1e18ee104bfae76af2e
It is available from 0.29.0
They have also included an example on UIExplorer.
Maybe is to late, but the best solution is to use a native library, IQKeyboardManager
Just drag and drop IQKeyboardManager directory from demo project to your iOS project. That's it. Also you can setup some valus, as isToolbar enabled, or the space between text input and keyboard in the AppDelegate.m file. More details about customisation are in the GitHub page link that I've added.
I used TextInput.onFocus and ScrollView.scrollTo.
...
<ScrollView ref="scrollView">
...
<TextInput onFocus={this.scrolldown}>
...
scrolldown: function(){
this.refs.scrollView.scrollTo(width*2/3);
},
#Stephen
If you don't mind not having the height animate at exactly the same rate that the keyboard appears, you can just use LayoutAnimation, so that at least the height doesn't jump into place. e.g.
import LayoutAnimation from react-native and add the following methods to your component.
getInitialState: function() {
return {keyboardSpace: 0};
},
updateKeyboardSpace: function(frames) {
LayoutAnimation.configureNext(animations.layout.spring);
this.setState({keyboardSpace: frames.end.height});
},
resetKeyboardSpace: function() {
LayoutAnimation.configureNext(animations.layout.spring);
this.setState({keyboardSpace: 0});
},
componentDidMount: function() {
KeyboardEventEmitter.on(KeyboardEvents.KeyboardDidShowEvent, this.updateKeyboardSpace);
KeyboardEventEmitter.on(KeyboardEvents.KeyboardWillHideEvent, this.resetKeyboardSpace);
},
componentWillUnmount: function() {
KeyboardEventEmitter.off(KeyboardEvents.KeyboardDidShowEvent, this.updateKeyboardSpace);
KeyboardEventEmitter.off(KeyboardEvents.KeyboardWillHideEvent, this.resetKeyboardSpace);
},
Some example animations are (I'm using the spring one above):
var animations = {
layout: {
spring: {
duration: 400,
create: {
duration: 300,
type: LayoutAnimation.Types.easeInEaseOut,
property: LayoutAnimation.Properties.opacity,
},
update: {
type: LayoutAnimation.Types.spring,
springDamping: 400,
},
},
easeInEaseOut: {
duration: 400,
create: {
type: LayoutAnimation.Types.easeInEaseOut,
property: LayoutAnimation.Properties.scaleXY,
},
update: {
type: LayoutAnimation.Types.easeInEaseOut,
},
},
},
};
UPDATE:
See #sherlock's answer below, as of react-native 0.11 the keyboard resizing can be solved using built in functionality.
You can combine a few of the methods into something a little simpler.
Attach a onFocus listener on your inputs
<TextInput ref="password" secureTextEntry={true}
onFocus={this.scrolldown.bind(this,'password')}
/>
Our scroll down method looks something like :
scrolldown(ref) {
const self = this;
this.refs[ref].measure((ox, oy, width, height, px, py) => {
self.refs.scrollView.scrollTo({y: oy - 200});
});
}
This tells our scroll view (remember to add a ref) to scroll to down to the position of our focused input - 200 (it's roughly the size of the keyboard)
componentWillMount() {
this.keyboardDidHideListener = Keyboard.addListener(
'keyboardWillHide',
this.keyboardDidHide.bind(this)
)
}
componentWillUnmount() {
this.keyboardDidHideListener.remove()
}
keyboardDidHide(e) {
this.refs.scrollView.scrollTo({y: 0});
}
Here we reset our scroll view back to the top,
I'm using a simpler method, but it's not animated yet. I have a component state called "bumpedUp" which I default to 0, but set to 1 when the textInput gets focus, like this:
On my textInput:
onFocus={() => this.setState({bumpedUp: 1})}
onEndEditing={() => this.setState({bumpedUp: 0})}
I also have style that gives the wrapping container of everything on that screen a bottom margin and negative top margin, like this:
mythingscontainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
},
bumpedcontainer: {
marginBottom: 210,
marginTop: -210,
},
And then on the wrapping container, I set the styles like this:
<View style={[styles.mythingscontainer, this.state.bumpedUp && styles.bumpedcontainer]}>
So, when the "bumpedUp" state gets set to 1, the bumpedcontainer style kicks in and moves the content up.
Kinda hacky and the margins are hardcoded, but it works :)
I use brysgo answer to raise the bottom of my scrollview. Then I use the onScroll to update the current position of the scrollview. I then found this React Native: Getting the position of an element to get the position of the textinput. I then do some simple math to figure out if the input is in the current view. Then I use scrollTo to move the minimum amount plus a margin. It's pretty smooth. Heres the code for the scrolling portion:
focusOn: function(target) {
return () => {
var handle = React.findNodeHandle(this.refs[target]);
UIManager.measureLayoutRelativeToParent( handle,
(e) => {console.error(e)},
(x,y,w,h) => {
var offs = this.scrollPosition + 250;
var subHeaderHeight = (Sizes.width > 320) ? Sizes.height * 0.067 : Sizes.height * 0.077;
var headerHeight = Sizes.height / 9;
var largeSpace = (Sizes.height - (subHeaderHeight + headerHeight));
var shortSpace = largeSpace - this.keyboardOffset;
if(y+h >= this.scrollPosition + shortSpace) {
this.refs.sv.scrollTo(y+h - shortSpace + 20);
}
if(y < this.scrollPosition) this.refs.sv.scrollTo(this.scrollPosition - (this.scrollPosition-y) - 20 );
}
);
};
},
I also meet this question. Finally, I resolve it by defining the height of each scene, such as:
<Navigator
...
sceneStyle={{height: **}}
/>
And, I also use a third-party module https://github.com/jaysoo/react-native-extra-dimensions-android to get the real height.