We want to display a looping slideshow of pictures that looks like a gif. The current result is visible at this url: https://figuredevices.com.
Our current approach is using opacity to show or hide slides:
class SlideShow extends React.Component {
constructor(props, context) {
super(props);
this.state = {
currentSlide: 0
};
this.interval = null;
}
componentDidMount() {
this.interval = setInterval(this.transitionToNextSlide.bind(this), 200);
}
componentWillUnmount(){
if(this.interval){
clearInterval(this.interval);
}
}
transitionToNextSlide() {
let nextSlide = this.state.currentSlide + 1;
if (nextSlide == this.props.slides.length) {
nextSlide = 0;
}
this.setState({currentSlide: nextSlide});
}
render () {
let slides = this.props.pictures.map((picture, idx) => {
let slideContainerStyle = {
opacity: this.state.currentSlide == idx ? 1 : 0
};
return(
<div style={slideContainerStyle} key={idx}>
<Slide picture={picture}/>
</div>
);
})
let containerStyle = {
width:'100%'
};
return (
<div style={containerStyle}>
{slides}
</div>
);
}
};
Pictures are loaded 5 by five into this.props.picture. The number of pictures is not bounded and I am worried about performance as this number grows. There are two things that don't feel right to me:
The map operation in the render method is traversing a whole array every 200ms only to change two css properties.
The DOM is growing a lot in size but most of nodes are hidden
Would you suggest a better approach, maybe using animation or react-motion ?
You should maintain all of your pictures as an array, and every 200ms, increment the array index. Then, instead of displaying all of the pictures every single time, just have it display the picture at your current index. This is a lot better because you're only returning one photo ever instead of a bunch of invisible ones :)
Note: I wasn't able to test this, so I'm not sure if everything is exactly syntactically correct, but this is the general idea. Every 200ms, increment the slideIndex. Then, always return a div with only the one picture you want to see.
class SlideShow extends React.Component {
constructor(props, context) {
super(props);
this.state = {
slideIndex;
};
this.interval = null;
}
componentDidMount() {
this.interval = setInterval(this.transitionToNextSlide.bind(this), 200);
}
componentWillUnmount(){
if(this.interval){
clearInterval(this.interval);
}
}
transitionToNextSlide() {
this.setState({this.state.slideIndex: (this.state.slideIndex + 1) % this.props.slides.length})
render () {
let containerStyle = {
width:'100%'
};
return (
<div style={containerStyle}>
<Slide picture={this.props.pictures[this.state.currentSlide]}/>
</div>
);
}
};
Related
I build an webpage with angular, each module is an component it has an animation in it but it run's only when the page opens but i need to perform the animation while the component is visibile on the screen. i just tried below like hide and show the component by checking the scrollY of the page. is there any better way to do it?
#HostListener('window:scroll', ['$event']) onWindowScroll(e: any) {
if (window.pageYOffset < 180) {
this.heroShown = 0;
} else {
this.heroShown = 1;
}
console.log(e.target['scrollingElement'].scrollTop);
console.log(document.body.scrollTop);
console.log(window.pageYOffset);
}
`
for that you can use a Intersection Observer.
The observer fires an event when the element is visible.
So when the event fires you can start your animation.
private createObserver() {
const options = {
rootMargin: '0px',
threshold: this.threshold,
};
const isIntersecting = (entry: IntersectionObserverEntry) =>
entry.isIntersecting || entry.intersectionRatio > 0;
this.observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (isIntersecting(entry)) {
this.subject$.next({ entry, observer });
}
});
}, options);
}
a other way to archive this is using a framwork like gsap
There you can use something like a scrolltrigger.
Check the docs here.
I'm actually trying to toggle a class on an element when user clicked. But unfortunately, my code only set class for single element. It looks like the view is not refreshing for subsequent clicks, even the set class is not removing. But my store is properly updating.
Here is my code.
class MyInterests extends Component {
constructor(props) {
super(props);
this.state = {selected: []};
}
toggleChip(id, index, event){
const { interestChanged } = this.props;
let index = this.state.selected.indexOf(id);
if(index === -1){
this.state.selected.push(id);
}else{
this.state.selected.splice(index, 1);
}
interestChanged(this.state.selected);
}
render() {
const {classes, myinterests: { categories, userinterest } } = this.props;
const getClassNames = (id) => {
return classNames(classes.chip, {[classes.selected]: (userinterest.indexOf(id) !== -1)});
}
return (
/*..... Few element to support styles....*/
{categories.map((data, index) => {
return (
<Chip
key={data._id.$oid}
label={data.name}
onClick={this.toggleChip.bind(this, data._id.$oid, index)}
className={getClassNames(data._id.$oid)}
/>
);
})}
);
}
}
Can anyone tell me what is wrong in this or how can I achieve this?.
Since state is immutable, you cannot use .push on it.
By using this.state.selected.push(id) you're mutating the state thus not signaling react on the change making the change vulnerable to future state updates (remember that setState is asynchronous and changes are batched for single action).
Take a look at this for how to solve it.
In your case, a better way to update the state would be something like this:
// create a copy of the current array
var selected = this.state.selected.slice();
// push the new element to the copy
selected.push(id);
// replace the current array with the modified copy
this.setState({ selected: selected });
I have a tree structure which I'm trying to query to a specific depth. I'm new to relay so not sure about if I'm going about this the right way or even if its possible.
My code is currently looking like this:
class TreeRoot extends React.Component {
render() {
var container = this.props.treeRoot;
return (
<div>
<ViewNode viewNode={container.root} maxDepth={10} expand={true}/>
</div>
);
}
}
class ViewNode extends React.Component {
render() {
var vn = this.props.viewNode;
return (
<div>
<div>{vn.type} {vn.widget} {vn.mode}</div>
<ViewNodeList viewNode={vn} maxDepth={this.props.maxDepth-1}/>
</div>
);
}
}
ViewNode = Relay.createContainer(ViewNode, {
initialVariables:{
maxDepth:1,
expand:false
},
fragments: {
viewNode: (variables) => Relay.QL`
fragment on ViewNode{
id
type
widget
mode
viewNodes #include(if: $expand){
${ViewNode.getFragment("viewNode", {maxDepth:(variables.maxDepth -1),expand:(variables.maxDepth > 0)}).if(variables.expand)}
}
}`,
}
});
class ViewNodeList extends React.Component {
render() {
const vn = this.props.viewNode;
if (!vn.viewNodes){
return (<div></div>);
}
return (
<div>
{vn.viewNodes.map((el, i)=> {
return <ViewNode key={i} viewNode={el} maxDepth={this.props.maxDepth} expand={this.props.maxDepth > 0}></ViewNode>
})
}
</div>
);
};
}
TreeRoot = Relay.createContainer(TreeRoot, {
fragments: {
root: () => Relay.QL`
fragment on TreeRoot{
id
name
root{
${ViewNode.getFragment('viewNode',{maxDepth:10,expand:true})}
}
}
`,
}
}
);
The significant bit being the way I'm trying to control the recursion in the ViewNode component's viewNode fragment. It is attempting to recurse down while decrementing the 'maxDepth' variable and using the 'maxDepth' to calculate the value of the 'expand' variable. Whether to continue recursing is based on the 'expand' var.
Currently this retrieves the root and the first level of children but doesn't recurse as desired. Is what I'm trying to do possible? If it is am I on the right track or going about this in completely the wrong way?
The typical pattern is to create a fragment for the content and then nest the elements in the query. E.g.
fragment ViewContent on ViewNode {
name
}
query ViewQuery {
root {
viewNode {
...ViewContent
viewNode {
...ViewContent
viewNode {
...ViewContent
}
}
}
}
}
I'm a little confused if I can use React Virtualized's Collection component to solve my problem. I'll try to describe what I'm doing:
I'm using React Virtualized on a page to display two lists/collections of items. I've finished the first collection which has items that have the same width and height:
The first collection was pretty straight forward and easy to implement.
Now I'm working on the second collection which contains images of varying sizes. I want the cells to have the same height but different widths (depending on the image dimensions of course). The problem is that rows might not always have the same number of cells:
Is this possible to achieve with React Virtualized? If so, how can I determine the position in "cellSizeAndPositionGetter"?
I recently used react-virtualized List to display rows of fixed-height, variable-width image cards and it worked great.
My List rowRenderer uses an array of rows of image card elements. That is, an array of arrays of react components, as JSX.
See my final function, cardsRows, for how I build the rows based on element widths and screen width.
Here's how it looks:
Hope this helps!
Some snippets of my code:
import {AutoSizer, List} from 'react-virtualized';
...
updateDimensions() {
this.setState({
screenWidth: window.innerWidth,
});
}
componentDidMount() {
window.addEventListener("resize", this.updateDimensions);
}
componentDidUpdate(prevProps, prevState) {
const props = this.props;
const state = this.state;
if (JSON.stringify(props.imageDocs) !== JSON.stringify(prevProps.imageDocs) || state.screenWidth !== prevState.screenWidth)
this.setState({
cardsRows: cardsRows(props, state.screenWidth),
});
}
rowRenderer({key, index, style, isScrolling}) {
if (!this.state.cardsRows.length)
return '';
return (
<div id={index} title={this.state.cardsRows[index].length} key={key} style={style}>
{this.state.cardsRows[index]}
</div>
);
}
...
render() {
return (
<div style={styles.subMain}>
<AutoSizer>
{({height, width}) => (<List height={height}
rowCount={this.state.cardsRows.length}
rowHeight={164}
rowRenderer={this.rowRenderer}
width={width}
overscanRowCount={2}
/>
)}
</AutoSizer>
</div>
);
}
...
const cardsRows = (props, screenWidth) => {
const rows = [];
let rowCards = [];
let rowWidth = 0;
const distanceBetweenCards = 15;
for (const imageDoc of props.imageDocs) {
const imageWidth = getWidth(imageDoc);
if (rowWidth + distanceBetweenCards * 2 + imageWidth <= screenWidth) {
rowCards.push(cardElement(imageDoc));
rowWidth += distanceBetweenCards + imageWidth;
}
else {
rows.push(rowCards);
rowCards = [];
rowWidth = distanceBetweenCards;
}
}
if (rowCards.length) {
rows.push(rowCards);
}
return rows;
};
const styles = {
subMain: {
position: 'absolute',
display: 'block',
top: 0,
right: 0,
left: 0,
bottom: 0,
}
};
I'm using Ember.js with ember-cli and ember-data. Until now, development went quite smoothly but now I encountered an issue with css transitions which I can't solve myself.
I have a list. The list contains elements which have subelements. These subelements are also rendered as a list.
I fetch the data with ember-data from a REST API. After the data is received I want to fade in (css opacity) the list. But this does not work correctly. Sometime the transition is shown and sometimes not. I'm afraid it is a timing issue. So I added Ember.run.next to my code but it didn't help. When I add setTimeout with 1ms inside Ember.run.next it works like expected (at least on my computer). This feels quite weird. Here is my code I have so far. Every feedback appreciated.
controller.js:
export default Ember.Controller.extend({
//...
objects: []
//...
_pushToMatchings: function (response) {
var tempArray = [];
var pushed = false;
for (var i = 0, length = this.get('objects.length'); i < length; i++) {
pushed = false;
var match = this.get('objects').objectAt(i);
if (match.get('meta.items').objectAt(0) === response.get('meta.items').objectAt(0)) {
tempArray.pushObject(response);
pushed = true;
} else {
tempArray.pushObject(match);
}
}
if (!pushed) {
tempArray.pushObject(response);
}
this.set('objects', tempArray);
},
fetch: function() {
var self = this;
// find parent item
this.get('store').find('item', id).then(function (item) {
self._pushToMatchings(Ember.Object.create({
meta: {
items: [id],
isLoading: true,
label: item.get('label')
},
content: []
}));
self.set('isOpen', true);
// child object
self.get('store').find('child', searchParams).then(function (result) {
(function (resultPtr) {
Ember.run.next(function () {
setTimeout(function () { // #todo why do we need timeout here? whitout there is no fade out with opacity in css possible
resultPtr.set('meta.isLoaded', true);
}, 1); // 1 is enough but give spinner some time otherwise it looks ugly
});
}(result));
result.set('meta.label', item.get('label'));
self._pushToMatchings(result);
}, function (error) { /* ... */ });
}, function (error) { /* ... */ });
}
}
controller.hbs:
<div>
{{item-list close="close" elements=objects }}
</div>
item-list.js
export default Ember.Component.extend({
elements: [],
actions: {
close: function () {
this.sendAction('close');
}
}
});
item-list.hbs
<div class="items-list__buttons">
<i class="icon-close_32" {{action "close" }}></i>
</div>
<div class="items-list__content">
{{#each matching in elements}}
<div class="items-list__item">
<h2>{{t "items.offers" }} {{matching.meta.label}}</h2>
{{spinner-element hideEvent=matching.meta.isLoaded }}
<div {{bind-attr class=":items-list__box matching.meta.isLoaded:items--fadeIn" }}>
{{#each item in matching.content}}
<div>
<!-- Render details of item -->
</div>
{{/each}}
</div>
</div>
{{/each}}
</div>
CSS:
.items-list__box {
opacity: 0;
transition: opacity 150ms ease 100ms;
}
.items--fadeIn {
opacity: 1;
}
You can use Ember.run.later, works same way than setTimeout.
Ember.run.later(this ,function(){
resultPtr.set('meta.isLoaded', true);
}, 100);
I'm not sure but this is neccesary because the div would be render with the class "items--fadeIn" that the transition wouldn't occur. I've done this way and worked for me, just try incrementing the time a little.
I know this is a late answer, but for others receiving a similar issue:
Your problem is that Ember is re-rendering your entire list of items in your {{#each because every time something changes you are giving it an entirely new array of objects, instead of changing the properties of the objects in the array. What you need to do is to define your array of objects and manipulate their properties so that only the objects that change get re-rendered.