React-Three-Fiber Animation Resets on Scroll - three.js

I created a low poly water animation using three.js and react-three-fiber. The animation begins to play when my webpage is loaded but as you start to scroll down to view the other content on my webpage, my animation resets and begins to start again.
PolyWater is just a component I created to make the low poly water using vertices.
The SeaScene is exported to a Home component that merges the rest of my components together.
My Home component is being Rendered in the App.js file in react using Router
SeaScene.js
import React, {useRef} from 'react'
import {Canvas, extend, useFrame, useThree} from "react-three-fiber"
import {OrbitControls} from "three/examples/jsm/controls/OrbitControls"
import PolyWater from "./PolyWater/PolyWater";
import './SeaScene.css'
extend({OrbitControls})
const Controls = () => {
const orbitRef = useRef();
const {camera, gl} = useThree();
useFrame(() => {
orbitRef.current.update()
camera.position.set(25, 12.5, -20)
camera.rotation.set(-1.5, 0, 0)
})
return (
<orbitControls
args={[camera, gl.domElement]}
ref={orbitRef}
/>
)
}
const SeaScene = () => {
return (
<section id="home" className="home-section">
<Canvas>
<ambientLight intensity={0.2}/>
<directionalLight color={0xffffff} position={[0, 50, 30]}/>
<Controls/>
<PolyWater/>
</Canvas>
</section>
)
}
Home.js
class Home extends Component {
render() {
return (
<div>
<SeaScene/>
<About/>
<Work/>
<Footer/>
</div>
);
}
}
App.js
class App extends Component {
render() {
return (
<Router>
<div>
<section>
<NavBar/>
<Switch>
<Route exact path='/' component={Home}/>
</Switch>
</section>
</div>
</Router>
);
};
}
Link to my working code: https://github.com/NikAtNight/waterportfolio/blob/master/homepage/src/components/MainAnimation/PolyWater/PolyWater.js

I found the fix myself. It was on react-three-fibers github just didn't know that would be the fix. I changed my materials and geometry from the regular way you declare them to the way below.
const geom = useMemo(() => new BoxBufferGeometry(), [])
const mat = useMemo(() => new MeshBasicMaterial(), [])
A link to the page
https://github.com/react-spring/react-three-fiber/blob/master/pitfalls.md

While the answer of #astronik is much better for overall performance. It only took me ages to replace every geometry and material.
So after some digging, I found a quick fix thanks to this comment by drcmda. Apparently, the canvas is always listening for scroll interactions in case of renderings. You can disable this behavior by simply setting scroll: false on the Canvas element.
<Canvas resize={{ scroll: false }} >
⚠️ The only downside using this quick fix, is that you can not use hover/click/scroll elements in the canvas anymore.

Related

Add css minHeight to multiple select in react material ui

I have created a material ui select multiple list and have some values in it. I want to increase the min height of the select, but after applying the minHeight css in the select tag, it does not increase the height of the select box.
This is the CodeSandBox for it. I have commented the place where i tried applying the css minHeight property.
If i inspect element in my browser and apply minHeight:'14rem' to the class="MuiSelect-root MuiSelect-select MuiInputBase-input MuiInput-input". It increases the size of the select box perfectly but not able to find its corresponding tag in actual code as in the browser inspect, it have more classes applied from the material ui library.
Here is my codeSandbox demo with comments
https://codesandbox.io/s/muddy-breeze-4mshf?file=/src/App.js
Try this:
import React from "react";
import "./styles.css";
import Grid from "#material-ui/core/Grid";
import { makeStyles } from "#material-ui/core/styles";
import FormControl from "#material-ui/core/FormControl";
import Select from "#material-ui/core/Select";
import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";
const useStyles = makeStyles(theme => ({
...
formControl: {
"& option": {
minHeight: 40
}
}
}));
export default function App() {
...
const classes = useStyles();
return (
<div className="App">
<Row>
{/* LEFT LIST 1 JSX */}
<Col lg="5">
<Grid container className={classes.root}>
<Grid item>
<FormControl className={classes.formControl}>
<span id="LIST_1">LEFT List 1</span>
<Select
multiple
native
// it increases the size but do not increase the space between the content resulting in bigger box but extra space at top and bottom
// style={{minHeight:'14rem'}}
value={selectedValue}
inputProps={{
id: "LIST_1"
}}
>
{leftlist1.map((leftlist1, index) => (
// if you apply in line 53 in options it increases the space between the option value
// style={{minHeight:'14rem'}}
<option key={index} value={leftlist1}>
{leftlist1} dsfsf
</option>
))}
</Select>
</FormControl>
</Grid>
</Grid>
</Col>
</Row>
</div>
);
}
You can update the specific class styles in the Select component by using withStyle. For more classes to update you can check Select API
import { withStyles } from "#material-ui/core/styles";
import MuiSelect from "#material-ui/core/Select";
const Select = withStyles(theme => ({
select: {
minHeight: "14rem"
}
}))(MuiSelect);
Working example :

React change navbar colour based on route

I am new to react. I am using react-route-dom and . I would like to change the colour of my NavBar functional component when the route changes.
In my NavBar component I am using the HOC (connect) from react-redux and withRouter from react-router to get route props and information from my redux store.
const mapStateToProps = (state) => {
return {
moduleCards: state.moduleCards
}
}
export default connect(mapStateToProps)(withRouter(NavBar));
Here is a snippet of how I am getting route information to then find the moduleCard entry and get it's colour.
const NavBar = (props) => {
console.log("Hello world?");
const modules = props.moduleCards.find(c => `/${c.name.replace(/ /g,'')}` === props.location.pathname);
const color = typeof modules !== 'undefined' ? modules.color : 'blue';
return (
<nav>
<div className={`nav-wrapper ${color} darken-2`}>
The problem is that the colour does not change when I change route. Can you please help with how to trigger a render on my NavBar component? Or if there is a better way to handle this?
Here is my root App component.
class App extends Component {
render() {
return (
<BrowserRouter>
<div className="App">
<NavBar/>
<Switch>
<Route exact path='/' component={Home}/>
<ModuleRoutes/>
</Switch>
</div>
</BrowserRouter>
);
}
}
Here is a snap shot of my store, this is where I am grabbing the colour from.
const initialState = {
moduleCards: [
{ id: 0, name: 'Client Register', icon: 'person', color: 'red' },
{ id: 1, name: 'Property Register', icon: 'domain', color: 'blue' },
The colour is blue on the home page and unless I refresh it remains blue. When I refresh it rerenders and the navbar gets its correct colour.
In documentation they wraps component first with redux and then with router
I propose you change your:
export default connect(mapStateToProps)(withRouter(NavBar));
to this:
export default withRouter(connect(mapStateToProps)(NavBar));

Displaying Images in ListView for React Native

I have two presentational components. The first, called Category simply renders a React Native ListView component after doing a bit of set-up work.
The second component, Book, simply displays the data as text, with an image that should be fetched through the network.
Unfortunately, the Image doesn't seem to be displaying at all. Can someone help me get the image to display? Below, please find the Category and Book component definition, as well as a sample of the props being past to Book.
Category.js
import React from 'react';
import { ListView, Text } from 'react-native';
import _ from 'lodash';
import { camelizeKeys } from 'humps';
import Book from './Book';
const Category = ({ category }) => {
let element;
if (!_.isEmpty(category)) { // if data is available
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
const camelCasedBooks = camelizeKeys(category.results.books);
const data = ds.cloneWithRows(camelCasedBooks);
element = (
<ListView
dataSource={data}
enableEmptySections
renderRow={(book) => <Book {...book} />}
/>
);
} else { // if data is not available
element = (
<Text>Loading</Text>
);
}
return element;
};
Category.propTypes = {
category: React.PropTypes.object.isRequired,
};
export default Category;
Book.js
import React from 'react';
import { Image, Text, View } from 'react-native';
const Book = ({ author, bookImage, description, title }) =>
<View>
<Text>
{author}
</Text>
<Text>
{bookImage}
</Text>
<Text>
{description}
</Text>
<Text>
{title}
</Text>
<Image source={{ uri: bookImage }} />
</View>;
Book.propTypes = {
author: React.PropTypes.string.isRequired,
bookImage: React.PropTypes.string.isRequired,
description: React.PropTypes.string.isRequired,
title: React.PropTypes.string.isRequired,
};
export default Book;
Sample Book Props
{
author: "Vi Keeland",
bookImage: "https://s1.nyt.com/du/books/images/9781942215462.jpg",
description: "Reese dismisses a chance encounter with a smug stranger, until he turns out to be her new boss.",
title: "BOSSMAN"
}
When using images that come from a remote location (such as your bookImage you need to set an explicit width and height on the Image's style. This is due to the fact that there is not automatic way for react-native to know how big the images is going to be.
Check the Image documentation for strategies on calculating the Image's size before being rendered. In my experience however simply setting the width and height explicitely is enough most of the times.

Re-rendering a single row of a list without re-rendering the entire list

we're trying to implement a contact list that works just like the new Material Design Google Contacts (you must enable the material design skin to see it) using material-ui.
Specifically we're trying to show a checkbox instead of the avatar on row hover.
We'd like to catch and re-render only the interested row (when hovered) and show the avatar/checkbox accordingly... this seems an easy task but we're not able to isolate the render to the hovered row (instead of re-rendering the entire list)
Do you have any suggestion on how to do something like this?
Our temporary solution uses a container component that handles the table:
When a row is hovered we capture it from onRowHover of the Table component and save it in the container state. This triggers a re-render of the entire list with really poor perfomance.
You can see a video of the issue here.
Here is a code sample:
import React from 'react'
import Avatar from 'material-ui/lib/avatar'
import Checkbox from 'material-ui/lib/checkbox'
import Table from 'material-ui/lib/table/table'
import TableHeaderColumn from 'material-ui/lib/table/table-header-column'
import TableRow from 'material-ui/lib/table/table-row'
import TableHeader from 'material-ui/lib/table/table-header'
import TableRowColumn from 'material-ui/lib/table/table-row-column'
import TableBody from 'material-ui/lib/table/table-body'
import R from 'ramda'
export default class ContactsList extends React.Component {
constructor (props) {
super(props)
this.state = { hoveredRow: 0 }
this.contacts = require('json!../../public/contacts.json').map((e) => e.user) // Our contact list array
}
_handleRowHover = (hoveredRow) => this.setState({ hoveredRow })
_renderTableRow = ({ hovered, username, email, picture }) => {
const checkBox = <Checkbox style={{ marginLeft: 8 }} />
const avatar = <Avatar src={picture} />
return (
<TableRow key={username}>
<TableRowColumn style={{ width: 24 }}>
{hovered ? checkBox : avatar}
</TableRowColumn>
<TableRowColumn>{username}</TableRowColumn>
<TableRowColumn>{email}</TableRowColumn>
</TableRow>
)
}
render = () =>
<Table
height='800px'
fixedHeader
multiSelectable
onRowHover={this._handleRowHover}
>
<TableHeader displaySelectAll enableSelectAll>
<TableRow>
<TableHeaderColumn>Nome</TableHeaderColumn>
<TableHeaderColumn>Email</TableHeaderColumn>
<TableHeaderColumn>Telefono</TableHeaderColumn>
</TableRow>
</TableHeader>
<TableBody displayRowCheckbox={false} showRowHover>
{this.contacts.map((contact, index) => this._renderTableRow({
hovered: index === this.state.hoveredRow,
...contact }))
}
</TableBody>
</Table>
}
Thank you in advance.
You could wrap your rows into a new component implementing shouldComponentUpdate like so :
class ContactRow extends Component {
shouldComponentUpdate(nextProps) {
return this.props.hovered !== nextProps.hovered || ...; // check all props here
}
render() {
const { username, email, ...otherProps } = this.props;
return (
<TableRow { ...otherProps } >
<TableRowColumn style={{ width: 24 }}>
{this.props.hovered ? checkBox : avatar}
</TableRowColumn>
<TableRowColumn>{this.props.username}</TableRowColumn>
<TableRowColumn>{this.props.email}</TableRowColumn>
</TableRow>
);
}
}
Then you can use it in your ContactList component like so :
this.contacts.map((contact, index) => <ContactRow key={contact.username} {...contact} hovered={index === this.state.hoveredRow} />)
If you don't want to manually implement shouldComponentUpdate, you can use React's PureRenderMixin or check a lib like recompose which provides useful helpers like pure to do so.
EDIT
As pointed out by the OP and #Denis, the approach above doesn't play well with some features of the Table component. Specifically, TableBody does some manipulation on its children's children. A better approach would be to define your ContactRow component like so:
class ContactRow extends Component {
shouldComponentUpdate(nextProps) {
// do your custom checks here
return true;
}
render() {
const { username, email, ...otherProps } = this.props;
return <TableRow { ...otherProps } />;
}
}
and then to use it like this
<ContactRow { ...myProps }>
<TableRowColumn>...</TableRowColumn>
</ContactRow>
But I guess having TableRow re-render only when necessary is a feature everyone would benefit from, so maybe a PR would be in order :)

Animated page transitions in react

The past couple of weeks I've been working on an app using React. So far everything is working fine, but now I want to add some transitions to it. These transitions are a bit more complex than any examples I managed to find.
I've got 2 pages, an overview and a detail page which I'd like to transition between.
I'm using react-router to manage my routes:
<Route path='/' component={CoreLayout}>
<Route path=':pageSlug' component={Overview} />
<Route path=':pageSlug/:detailSlug' component={DetailView} />
</Route>
Overview looks like this:
Detailview looks like this:
The idea of the transition is that you click on one of the elements of the Overview. This element which has been clicked moves towards the position it should have on the detailView. The transition should be initiated by a route change (I think) and should also be able to happen in reverse.
I've already tried using ReactTransitionGroup on the Layout, which has a render method which looks like this:
render () {
return (
<div className='layout'>
<ReactTransitionGroup>
React.cloneElement(this.props.children, { key: this.props.location.pathname })
</ReactTransitionGroup>
</div>
)
}
This will give the child component the ability to receive the special lifecycle hooks. But I'd like to access the child components somehow during these hooks and still keep doing things the React way.
Could someone point me in the right direction for the next step to take? Or maybe point me to an example which I may have missed somewhere? In previous projects I used Ember together with liquid fire to get these kinds of transitions, is there maybe something like this for React?
I'm using react/react-redux/react-router/react-router-redux.
Edit: Added a working example
https://lab.award.is/react-shared-element-transition-example/
(Some issues in Safari for macOS for me)
The idea is to have the elements to be animated wrapped in a container that stores its positions when mounted. I created a simple React Component called SharedElement that does exactly this.
So step by step for your example (Overview view and Detailview):
The Overview view gets mounted. Each item (the squares) inside the Overview is wrapped in the SharedElement with a unique ID (for example item-0, item-1 etc). The SharedElement component stores the position for each item in a static Store variable (by the ID you gave them).
You navigate to the Detailview. The Detailview is wrapped into another SharedElement that has the same ID as the item you clicked on, so for example item-4.
Now this time, the SharedElement sees that an item with the same ID is already registered in its store. It will clone the new element, apply the old elements position to it (the one from the Detailview) and animates to the new position (I did it using GSAP). When the animation has completed, it overwrites the new position for the item in the store.
Using this technique, it's actually independent from React Router (no special lifecycle methods but componentDidMount) and it will even work when landing on the Overview page first and navigating to the Overview page.
I will share my implementation with you, but be aware that it has some known bugs. E.g. you have to deal with z-indeces and overflows yourself; and it doesn't handle unregistering element positions from the store yet. I'm pretty sure if someone can spend some time on this, you can make a great little plugin out of it.
The implementation:
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import Overview from './Overview'
import DetailView from './DetailView'
import "./index.css";
import { Router, Route, IndexRoute, hashHistory } from 'react-router'
const routes = (
<Router history={hashHistory}>
<Route path="/" component={App}>
<IndexRoute component={Overview} />
<Route path="detail/:id" component={DetailView} />
</Route>
</Router>
)
ReactDOM.render(
routes,
document.getElementById('root')
);
App.js
import React, {Component} from "react"
import "./App.css"
export default class App extends Component {
render() {
return (
<div className="App">
{this.props.children}
</div>
)
}
}
Overview.js - Note the ID on the SharedElement
import React, { Component } from 'react'
import './Overview.css'
import items from './items' // Simple array containing objects like {title: '...'}
import { hashHistory } from 'react-router'
import SharedElement from './SharedElement'
export default class Overview extends Component {
showDetail = (e, id) => {
e.preventDefault()
hashHistory.push(`/detail/${id}`)
}
render() {
return (
<div className="Overview">
{items.map((item, index) => {
return (
<div className="ItemOuter" key={`outer-${index}`}>
<SharedElement id={`item-${index}`}>
<a
className="Item"
key={`overview-item`}
onClick={e => this.showDetail(e, index + 1)}
>
<div className="Item-image">
<img src={require(`./img/${index + 1}.jpg`)} alt=""/>
</div>
{item.title}
</a>
</SharedElement>
</div>
)
})}
</div>
)
}
}
DetailView.js - Note the ID on the SharedElement
import React, { Component } from 'react'
import './DetailItem.css'
import items from './items'
import { hashHistory } from 'react-router'
import SharedElement from './SharedElement'
export default class DetailView extends Component {
getItem = () => {
return items[this.props.params.id - 1]
}
showHome = e => {
e.preventDefault()
hashHistory.push(`/`)
}
render() {
const item = this.getItem()
return (
<div className="DetailItemOuter">
<SharedElement id={`item-${this.props.params.id - 1}`}>
<div className="DetailItem" onClick={this.showHome}>
<div className="DetailItem-image">
<img src={require(`./img/${this.props.params.id}.jpg`)} alt=""/>
</div>
Full title: {item.title}
</div>
</SharedElement>
</div>
)
}
}
SharedElement.js
import React, { Component, PropTypes, cloneElement } from 'react'
import { findDOMNode } from 'react-dom'
import TweenMax, { Power3 } from 'gsap'
export default class SharedElement extends Component {
static Store = {}
element = null
static props = {
id: PropTypes.string.isRequired,
children: PropTypes.element.isRequired,
duration: PropTypes.number,
delay: PropTypes.number,
keepPosition: PropTypes.bool,
}
static defaultProps = {
duration: 0.4,
delay: 0,
keepPosition: false,
}
storeNewPosition(rect) {
SharedElement.Store[this.props.id] = rect
}
componentDidMount() {
// Figure out the position of the new element
const node = findDOMNode(this.element)
const rect = node.getBoundingClientRect()
const newPosition = {
width: rect.width,
height: rect.height,
}
if ( ! this.props.keepPosition) {
newPosition.top = rect.top
newPosition.left = rect.left
}
if (SharedElement.Store.hasOwnProperty(this.props.id)) {
// Element was already mounted, animate
const oldPosition = SharedElement.Store[this.props.id]
TweenMax.fromTo(node, this.props.duration, oldPosition, {
...newPosition,
ease: Power3.easeInOut,
delay: this.props.delay,
onComplete: () => this.storeNewPosition(newPosition)
})
}
else {
setTimeout(() => { // Fix for 'rect' having wrong dimensions
this.storeNewPosition(newPosition)
}, 50)
}
}
render() {
return cloneElement(this.props.children, {
...this.props.children.props,
ref: element => this.element = element,
style: {...this.props.children.props.style || {}, position: 'absolute'},
})
}
}
I actually had a similar problem, where I had a search bar and wanted it to move and wrap to a different size and place on a specific route (like a general search in the navbar and a dedicated search page). For that reason, I created a component very similar to SharedElement above.
The component expects as props, a singularKey and a singularPriority and than you render the component in serval places, but the component will only render the highest priority and animate to it.
The component is on npm as react-singular-compoment
And here is the GitHub page for the docs.

Resources