Does deep nesting flexbox layout cause performance issue? - css

I have been working on a ReactJS project where I create most of the components using flexbox layout. Since with react, we can have deeply nested components, so my layout is having nested flexbox layout.
Now my question is, does this have any issue with performance? On a single page, there are many components and each component have 3 to 4 level nested flexbox layout. Will that cause a performance issue?

Have done a little test. Rendered 100 components, each with 10 nested layout. With and without flexbox. Here are the code snippets:
Component/index.js
#CSSModules(styles, { allowMultiple: true })
export default class TheComponent extends Component {
render() {
const { deepNest, flex } = this.props
return (
<div>{ this.renderComp(deepNest, flex) }</div>
)
}
renderComp(deepNest, flex) {
const flexProperties = [
{ justifyContent: "center", alignItems: "center" },
{ justifyContent: "flex-start", alignItems: "flex-end" },
{ flexDirection: "row" }
]
const content = [
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus interdum quis ligula vel elementum. Integer non rhoncus purus, eget dignissim ante.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus interdum quis ligula vel elementum. Integer non rhoncus purus, eget dignissim ante. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus interdum quis ligula vel elementum. Integer non rhoncus purus, eget dignissim ante."
]
if (deepNest > 0 && flex) {
return (
<div styleName="containerFlex" style={flexProperties[deepNest % 3]}>
<div styleName="contentFlex" style={flexProperties[deepNest % 3]}>
{ content[deepNest % 3] }
</div>
<div styleName="nestedFlex" style={flexProperties[deepNest % 3]}>
{ this.renderComp(deepNest - 1, flex) }
</div>
</div>
)
}
if (deepNest > 0 && !flex) {
return (
<div styleName="container">
<div styleName="content">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus interdum quis ligula vel elementum. Integer non rhoncus purus, eget dignissim ante.
</div>
<div styleName="nested">
{ this.renderComp(deepNest - 1, flex) }
</div>
</div>
)
}
}
}
WithFlex/index.js
import TheComponent from "../Component"
#CSSModules(styles, { allowMultiple: true })
export default class WithFlex extends Component {
constructor(props) {
super(props)
this.state = { render: false }
}
render() {
const {render} = this.state
// number of components to render
const arr = _.range(100)
return (
<div>
<div
style={{ display: "block", padding: 30, lineHeight: "60px" }}
onClick={() => this.setState({render: !render})}>
Start Render
</div>
{ render && arr.map((i) => <TheComponent key={i} deepNest={10} flex={true}/> ) }
</div>
)
}
}
WithoutFlex/index.js
import TheComponent from "../Component"
#CSSModules(styles, { allowMultiple: true })
export default class WithoutFlex extends Component {
constructor(props) {
super(props)
this.state = { render: false }
}
render() {
const {render} = this.state
// number of components to renders
const arr = _.range(100)
return (
<div>
<div
style={{ display: "block", padding: 30, lineHeight: "60px" }}
onClick={() => this.setState({render: !render})}>
Start Render
</div>
{ render && arr.map((i) => <TheComponent key={i} deepNest={10} flex={false}/> ) }
</div>
)
}
}
Results from Chrome dev-tool timeline.
WithFlex
WithoutFlex
Summary
The difference is not that much. Also in flexbox, I put random properties to choose from. So I think it's alright with the performance. Hope it will help other devs.

Old flexbox (display: box) is 2.3x slower than new flexbox (display: flex).
Regular block layout (non-float), will usually be as fast or faster than new flexbox since it’s always single-pass. But new flexbox should be faster than using tables or writing custom JS-base layout code.
For more info
Article1
Article2

Related

How to make React Material-UI Accordion fill entire height?

I have 2 Material-ui Accordions which I want to expand to their full height in the following way:
Both of them collapsed.
One collapsed (take the maximum height possible)
Both expanded (each take 50% of the full height)
Can I achieve this with the following code simply with CSS? (thought it should be possible with flex-grow and flex-direction:column but I can't get it to work.
https://codesandbox.io/s/upbeat-tesla-uchsb?file=/accordionFullHeight
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Accordion from '#material-ui/core/Accordion';
import AccordionSummary from '#material-ui/core/AccordionSummary';
import AccordionDetails from '#material-ui/core/AccordionDetails';
import Typography from '#material-ui/core/Typography';
import ExpandMoreIcon from '#material-ui/icons/ExpandMore';
const useStyles = makeStyles((theme) => ({
root: {
width: '100%',
background: 'green',
height: '90vh'
},
heading: {
fontSize: theme.typography.pxToRem(15),
fontWeight: theme.typography.fontWeightRegular,
},
}));
export default function SimpleAccordion() {
const classes = useStyles();
return (
<div className={classes.root}>
<Accordion>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1a-content"
id="panel1a-header"
>
<Typography className={classes.heading}>Accordion 1</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex,
sit amet blandit leo lobortis eget.
</Typography>
</AccordionDetails>
</Accordion>
<Accordion>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel2a-content"
id="panel2a-header"
>
<Typography className={classes.heading}>Accordion 2</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex,
sit amet blandit leo lobortis eget.
</Typography>
</AccordionDetails>
</Accordion>
</div>
);
}
Solved it using a controlled Accordion that uses flex-grow:1 only when its expanded:
https://codesandbox.io/s/upbeat-tesla-uchsb?file=/src/MyAccordion.js
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import Accordion from "#material-ui/core/Accordion";
import AccordionDetails from "#material-ui/core/AccordionDetails";
import AccordionSummary from "#material-ui/core/AccordionSummary";
import Typography from "#material-ui/core/Typography";
import ExpandMoreIcon from "#material-ui/icons/ExpandMore";
const useStyles = makeStyles((theme) => ({
root: {
width: "100%",
background: "red",
color: "blue"
},
rootExpanded: {
background: "blue",
flexGrow: 1
}
}));
export default function MyAccordion(props) {
const classes = useStyles();
const { name } = props;
const [expanded, setExpanded] = React.useState(false);
const rootClass = expanded ? classes.rootExpanded : classes.root;
const handleChange = (panel) => (event, isExpanded) => {
setExpanded(isExpanded ? panel : false);
};
return (
<Accordion
className={rootClass}
expanded={expanded === name}
onChange={handleChange(name)}
>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1bh-content"
id={`${name}-header`}
>
<Typography className={classes.heading}>General settings</Typography>
<Typography className={classes.secondaryHeading}>
I am an accordion
</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>
Nulla facilisi. Phasellus sollicitudin nulla et quam mattis feugiat.
Aliquam eget maximus est, id dignissim quam.
</Typography>
</AccordionDetails>
</Accordion>
);
}

How to animate parent width depending on its content?

I have a modal whose content can be toggled between two different components. It works, but the transition is brutal since the wrapper immediately takes the width of its content.
How to animate this properly? I thought about transform:scale but it did not work.
Here is the code, as well as a sandbox:
import React, {useState} from "react";
import styled from "styled-components"
const Wrapper = styled.div`
background:gainsboro;
width:100%;
height:100%;
border:1px solid lightgrey;
`
const Content1 = () => (
<div>
Lorizzle ipsizzle dolor sit amet, ass adipiscing elizzle. Ass
izzle velizzle, volutpizzle, suscipit quizzle, we gonna chung
vizzle, arcu. Pellentesque egizzle boom shackalack. Fo shizzle
my nizzle erizzle. Pimpin' crunk dolor dapibus rizzle tempizzle
sizzle. Maurizzle fo shizzle mah nizzle fo rizzle, mah home
g-dizzle nibh daahng dawg go to hizzle. Shizznit izzle tortor.
Pellentesque sizzle rhoncizzle shizzlin dizzle. In hizzle
habitasse platea dictumst. For sure fo. Break it down izzle
urna, pretizzle eu, mattis go to hizzle, eleifend black, nunc.
Daahng dawg suscipit. Tellivizzle yo mamma velit sed check
out this.
</div>
)
const Content2 = () => (
<div>
very short content
</div>
)
export default function App() {
const [toggle, setToggle] = useState(false)
return (
<Wrapper>
{toggle ? <Content1 /> : <Content2/>}
<button onClick={()=> setToggle(!toggle)}>toggle content</button>
</Wrapper>
);
}
https://codesandbox.io/s/still-smoke-5ikkg?file=/src/App.js
Thanks!
you can use react-fade-in, not the best, but a fast solution
https://www.npmjs.com/package/react-fade-in

Specifying alternate background image using ngStyle

I am trying to specify an alternate background image for a DIV like so:
<div [ngStyle]="{'background-image':'url(1.jpg), url(2.jpg)'}"></div>
Neither of the images are displaying (it works if I don't specify an alternate image).
Is it possible to specify multiple background images using ngStyle?
Working demo
Template file
<div [ngStyle]='styles'>
<h1>Lorem Ipsum Dolor</h1>
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.</p>
</div>
Ts file
export class AppComponent {
name = 'Angular';
img1 = 'https://www.w3schools.com/css/img_flwr.gif';
img2 = 'https://www.w3schools.com/css/paper.gif'
isSelected: boolean = true;
styles = {};
setCurrentStyles() {
this.styles = {
backgroundImage: this.isSelected ?
`url(${this.img1})`:`url(${this.img2})`
};
}
toogleImage() {
this.isSelected = !this.isSelected;
this.setCurrentStyles();
}
}
Try like this
template.html
<div class="image" [ngStyle]="{background: !isActive ? 'url(https://www.fonewalls.com/wp-content/uploads/Aqua-Solid-Color-Background-Wallpaper-for-Mobile-Phone.png)' : 'url(https://www.fonewalls.com/wp-content/uploads/Midnight-Blue-Solid-Color-Background-Wallpaper-for-Mobile-Phone.png)'}"></div>
cmponent.ts
isActive: boolean = true;
You can also keep your HTML clean with moving all the logic into the component.ts.
In the end you would have something like this:
<div class="image" [ngStyle]="{
'background-image': 'url(' + backgroundImageString + ')'
}"></div>
Then in your component:
private defineBackImage(someArg) {
if (stuff) {
this.backgroundImageString = url1;
} else {
this.backgroundImageString = url2;
}
}
You can trigger this function on init of according to specific events, also you can extend this logic to display much more than 2 backgrounds

Polymer 3 - Google Maps

I would like to know how to include Google Map in Polymer 3. I have just upgraded to Polymer 3 from Polymer 2. This is my sample that is not working base of the starter-kit
import { PolymerElement, html } from '#polymer/polymer/polymer-element.js';
import '#em-polymer/google-map/google-map.js';
import '#em-polymer/google-map/google-map-marker.js';
import './shared-styles.js';
class MyView1 extends PolymerElement {
static get template() {
return html`
<style include="shared-styles">
:host {
display: block;
padding: 10px;
}
google-map {
height: 600px;
width: 600px;
}
</style>
<google-map latitude="37.779" longitude="-122.3892" min-zoom="9" max-zoom="11" language="en" api-key="XYZ">
</google-map>
<div class="card">
<div class="circle">1</div>
<h1>View One</h1>
<p>Ut labores minimum atomorum pro. Laudem tibique ut has.</p>
<p>Lorem ipsum dolor sit amet, per in nusquam nominavi periculis, sit elit oportere ea.Lorem ipsum dolor sit amet, per in nusquam nominavi periculis, sit elit oportere ea.Cu mei vide viris gloriatur, at populo eripuit sit.</p>
</div>
`;
}
}
window.customElements.define('my-view1', MyView1);
I get the following 2 errors:
element-mixin.js:322 template getter must return HTMLTemplateElement
and
Uncaught TypeError: Cannot read property 'api' of undefined
at HTMLElement._initGMap (google-map.js:480)
at HTMLElement.attached (google-map.js:457)
at HTMLElement.attached (class.js:262)
at HTMLElement.connectedCallback (legacy-element-mixin.js:117)
at HTMLElement._attachDom (element-mixin.js:653)
at HTMLElement._readyClients (element-mixin.js:620)
at HTMLElement._flushClients (property-effects.js:1749)
at HTMLElement.ready (property-effects.js:1853)
at HTMLElement.ready (element-mixin.js:604)
at HTMLElement._enableProperties (properties-changed.js:363)
#em-polymer/google-map/google-map.js was
import { Polymer } from '../../#polymer/polymer/lib/legacy/polymer-fn.js';
import { IronResizableBehavior } from '../../#polymer/iron-resizable-behavior/iron-resizable-behavior.js';
import '../google-apis/google-maps-api.js';
import './google-map-marker.js';
Polymer({
_template: `
<style>
and fix is
import { Polymer } from '../../#polymer/polymer/lib/legacy/polymer-fn.js';
import { IronResizableBehavior } from '../../#polymer/iron-resizable-behavior/iron-resizable-behavior.js';
import '../google-apis/google-maps-api.js';
import './google-map-marker.js';
import { html } from '#polymer/polymer/lib/utils/html-tag.js';
Polymer({
_template: html`
<style>

Animate dynamic height with AngularJS

So, I have been trying to animate kind of like a Angular Accordion, but with no success. I figured it out with fixed heights, but not with dynamic. height: auto; does not work. :(
Maybe some of you have had a similar problem?
My code:
html:
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular-route.js"></script>
<link rel="stylesheet" href="style.css">
<script src="script.js"></script>
</head>
<body>
<section ng-app="myApp">
<div ng-controller="myCtrl as vm">
<ul ng-init="vm.tab=1">
<li ng-repeat="item in vm.data">
<a href ng-click="vm.tab = item.thingy">{{item.name}}</a>
<div ng-show="vm.tab === item.thingy">
<img ng-src="{{item.img}}" width="50px"><br>
<div class="longDiv">{{item.description}}</div>
</div>
</li>
</ul>
</div>
</section>
</body>
</html>
js:
var app = angular.module('myApp', []);
app.controller('myCtrl', ['$scope',
function($scope) {
var vm = this;
vm.data = [{
name: "First",
title: "oneTitle",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur.",
year: "2013",
img: "http://static.hdw.eweb4.com/media/wp_400/1/5/42735.jpg",
thingy: 1
}, {
name: "third",
title: "twoTitle",
description: "Quisque pulvinar libero sed eros ornare",
year: "2014",
img: "http://static.hdw.eweb4.com/media/wp_400/1/1/8519.jpg",
thingy: 2
}, {
name: "Second",
title: "threeTitle",
description: "Cras accumsan ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur massa vitae tortor vehicula .",
year: "2015",
img: "http://static.hdw.eweb4.com/media/wp_400/1/5/43326.jpg",
thingy: 3
}, {
name: "fourth",
title: "FourTitle",
description: "Suspendisse ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur vitae mattis magna.",
year: "2011",
img: "http://static.hdw.eweb4.com/media/wp_400/1/5/42413.jpg",
thingy: 4
}];
}
]);
Thanks in advance!
Plnkr here
I've solved this problem by creating a "collapse" directive:
ngModule.directive('collapse', [function () {
return {
restrict: 'A',
link: function ($scope, ngElement, attributes) {
var element = ngElement[0];
$scope.$watch(attributes.collapse, function (collapse) {
var newHeight = collapse ? 0 : getElementAutoHeight();
element.style.height = newHeight +"px";
ngElement.toggleClass('collapsed', collapse);
});
function getElementAutoHeight() {
var currentHeight = getElementCurrentHeight();
element.style.height = 'auto';
var autoHeight = getElementCurrentHeight();
element.style.height = currentHeight +"px";
getElementCurrentHeight(); // Force the browser to recalc height after moving it back to normal
return autoHeight;
}
function getElementCurrentHeight() {
return element.offsetHeight
}
}
};
}]);
To use the directive, you just throw it on your element and set it to the scope variable that says whether or not to collapse it:
<div collapse="isCollapsed">
This will be collapsed
</div>
Now you just need to apply a CSS transition on the height and it will smoothly collapse & expand.
Since this directive also adds a class, you can apply other transitions like opacity.
Here's a Codepen with an example: http://codepen.io/KaidenR/pen/GoRJLx
Modified #kaiden answer, if using angular directive can use
require('./').directive('collapse', collapseDirective);
/**
* #ngInject
*/
function collapseDirective() {
return {
restrict: 'A',
link: link
};
function link($scope, ngElement, attributes) {
var element = ngElement[0];
// set the height as a data attr so we can use it again later
element.dataset.heightOld = angular.element(element).prop('offsetHeight');
$scope.$watch(attributes.collapse, function (collapse) {
var newHeight = !collapse ? 0 : element.dataset.heightOld;
element.style.height = newHeight + 'px';
ngElement.toggleClass('collapsed', collapse);
});
}
}
As the link function then just add CSS transitions for height. This sets a data attribute to store the height then uses it when toggling. In my application I used some markup like
<ion-item class="item-accordion" collapse="vm.isActive($index)">
Note this is inside an ng-repeat and calls a function on view model. This is also an ionic project hence the ion item tag.
You can try setting max-height to 0 and then remove the style/class as suggested here:
http://css3.bradshawenterprises.com/animating_height/
So, after long hours of trying to figure out what is the easiest and shortest way to make jQuery accordion with angular content, I figured one easy, but messy way.
Basically I made a new variable in my $scope- accordion, and put in there all accordion html:
vm.data = [{
name : "First",
accordion: ["<h3>Title1</h3><div>Content 1</div><h3>Title2</h3><div>Content 1</div>"]};
For me this works, because now I can use this variable in ng-repeat:
<div ng-repeat="item in vm.data>
<div ng-repeat="acc in item.accordion" ng-bind-html="acc | normalHtml"></div>
And as long there is only one string in this variable, I get one accordion div.
After that I called accordion function inside a setTimout function and everything works fine.
BUT this is messy and kind of "with a hammer where I could've used a feather" situation.
So, for now this is fine for me, but I am still looking for a prettier solution.
Thank you all for helping me out. If some of you have any idea how to make this better, I welcome every answer :)
Ok I think I found the holy grail. At least for my uses :)
I was having trouble using nested collapsed elements with the solution from #Kaiden and #Gareth Fuller, as any outer collapse element would keep the original height that it got before any inner collapse elements were collapsed. So I got inspired from #Icycool's suggestion and changed to animate on max-height, and keep height set to 'auto', which fixes that.
You can run a working demo below.
var testModule = angular.module('testModule', []);
testModule.controller('testController', function($scope) {
$scope.isCollapsed = false;
$scope.toggle = function() {
$scope.isCollapsed = !$scope.isCollapsed;
};
});
testModule.directive('collapse', [function() {
return {
restrict: 'A',
link: function link($scope, $element, $attrs) {
var element = $element[0];
$element.addClass('collapse');
const originalHeight = $element.prop('offsetHeight');
$scope.$watch($attrs.collapse, function(collapse) {
if (collapse) {
element.style.maxHeight = '0px';
} else {
element.style.maxHeight = originalHeight + 'px';
}
$element.toggleClass('sh-collapsed', collapse);
});
}
};
}]);
.collapse {
height: 'auto';
overflow: hidden;
transition: max-height 0.3s;
background: lightgreen;
}
<script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.4/angular.min.js"></script>
<div ng-app="testModule" ng-controller="testController">
<button type="button" ng-click="toggle()">Toggle</button> Collapsed: {{isCollapsed}}
<ul collapse="isCollapsed">
<li>Line One</li>
<li>Line Two</li>
<li>Line Three</li>
<li>Line Four</li>
<li>Line Five</li>
</ul>
</div>

Resources