0

The designer I am working with would really like the rows of a multi-columned set of text to be equal. It is for a fairly simple eBook, but when I get the height, sometimes it renders correctly and at other times it doesn't. Image for reference:

enter image description here

So, what I am targeting (and is also working some of the time) is for the rendered header to loop through and set the height to the tallest of the three headers. In the image above, the function should have rendered a similar view, but where each of the headers retains its margin. Instead, it has calculated them all to be the same size so the margin is cut. Unfortunately, it seems that (usually) on headers that have just one orphan, the height is being calculated in correctly. Does anyone have any better ideas? My code is below.

Columns.js (updateHeight being the important function here) —

import React from 'react' import { branch } from 'baobab-react/higher-order' import classNames from 'classnames' import _parseInt from 'lodash.parseint' import _filter from 'lodash.filter' import InlineSVG from 'react-inlinesvg' // Column Components import ImageContainer from './columns/ImageContainer' import IconContainer from './columns/IconContainer' import ColumnHeader from './columns/ColumnHeader' import Content from './columns/Content' class Columns extends React.Component {	constructor(props, context) {	super(props, context)	this.state = {	content: 0,	header: 0,	}	}	updateHeight(height, component) {	if (window.innerWidth > 768 && window.innerHeight > 768) {	if (height > this.state[component]) {	this.setState({ [component]: height })	} else {	return false	}	} else {	return false	}	}	getItems(column, index) {	let data = column[0] || column	const icon = data.icon ? '/assets/icons/' + data.icon : null	const image = data.bioImage ? '/assets/bio-photos/' + data.bioImage : null	const ref = `column${ index }`	return (	<div className="column" key={ index } ref={ ref }>	{ image ? <ImageContainer title={ data.title } image={ image } /> : null }	{ icon ? <IconContainer icon={ icon } /> : null }	<ColumnHeader	title={ data.title }	subtitle={ data.subtitle }	index={ index }	updateHeight={ this.updateHeight.bind(this) }	height={ index === undefined ? 'auto' : this.state.header } />	<Content	content={ data.content }	updateHeight={ this.updateHeight.bind(this) }	height={ index === undefined ? 'auto' : this.state.content } />	{ this.props.type === 'team'	? <div className="team__social"><InlineSVG src="/assets/icons/linkedin.svg"></InlineSVG></div>	: null	}	</div>	)	}	render() {	const data = this.props.data.columns || this.props.data	const type = this.props.type	const count = data.length || 1	let columns = count > 1 ? data.map((column, index) => this.getItems(column, index)) : this.getItems(data, 1)	let columnClasses = classNames({	'columns': true,	[`columns--${count}`]: true,	'columns--icons': type === 'icons' ? true : false,	'columns--team': type === 'team' ? true : false,	})	return (	<div className={ columnClasses }>	{ columns }	</div>	)	} } export default branch(Columns, {	cursors: {	navOpen: ['navOpen'],	} })

ColumnHeader.js —

import React from 'react' import { branch } from 'baobab-react/higher-order' class ColumnHeader extends React.Component {	constructor(props, context) {	super(props, context)	}	componentDidMount() {	this.props.updateHeight(this.refs.header.offsetHeight, 'header')	}	render() {	let { title, subtitle } = this.props	let height = (this.props.height === 0) ? 'auto' : this.props.height	return (	<div className="column__header" ref="header" style={{ height: height }}>	<h1 className="title">{ title }</h1>	{ subtitle ? (<h2 className="subtitle">{ subtitle }</h2>) : null }	</div>	)	} } export default ColumnHeader

Second picture for clarity: enter image description here

4
  • All elements in your picture (icon, header, subheader, text) seem to be of equal height (as required), with content inside aligned to top. What exactly do you want different? Commented Apr 6, 2016 at 16:31
  • It only appears that way (as I may have poorly explained above) because there is a 1rem margin on each header item. So, the height is actually cutting off the two headers on the right, but the margin is propping them up so that the overall space they site in is something like 68px, but the actualy height of the header is only like 42px, which is well below the actual height of the header. Realistically, all three should be 68px in height, for example, with a margin bottom of 1rem for each (if it worked correctly). Commented Apr 6, 2016 at 18:45
  • Sorry but I still don't see it. CSS Height = content + padding + margin. If height is always 68px, then height is the same for all headers, even though margin is different. And then all elements are aligned. Could you share clearer pictures of wrong and right layout? Commented Apr 6, 2016 at 20:17
  • @wintvelt picture added. so yes, they are all the same height, but they should be the same height, and have a margin. as you see, the one on the far right has a height that is smaller than it's actual height. the code should be picking the largest of the three columns, then applying that height to each header, but the height it is calculating is seemingly incorrect as you can see. Commented Apr 6, 2016 at 20:34

1 Answer 1

3

This is a css issue: offsetHeight and height have different definitions in css, which are important to understand. If you use read one and use that value to set the other, things go awry. Doesn't matter if you use react to do this or any other framework.

  • offsetHeight = content + padding + border (but excludes margin)
  • height = content + padding + border + margin

Because the height that is read excludes margins, it is too small. Therefore css compensates by sacrificing margin to display the content.

In an example:

  • let's assume your high header has padding of 0, margins of 10px top and bottom, and content with 2 lines and content-height of 48px.
  • So total height is 10+48+10 = 68px.
  • the offsetHeight you read is then 48px. (excludes margins)
  • you then set height to 48px. The resulting total height, including margin, is now set to 48px.
  • This is the wrong value. Correct value should have been 68px.

To fix, you need to include the top and bottom margin to the offsetHeight before passing height to the updateHeight() method:

componentDidMount() { // get topMargin and bottomMargin from DOM let heightPlusMargins = this.refs.header.offsetHeight + topMargin + bottomMargin; this.props.updateHeight(heightPlusMargins, 'header') } 

You can use jQuery or vanilla javascript to get the top and bottom margin. Both are explained in a separate thread on SO here.

On a more general note: you overall approach to get rows of equal height seems very inefficient. You first render the columns, then read height from all cells, and then adjust height and re-render. There is probably a better overall solution to solve the height issue in css only.
Looks like your columns are of equal width, and therefore all cells are of equal width.
You could also render by row (instead of by column), using only css to give each cell the same width (% or fixed) and to make sure each cell in the same row is of equal height. HTML tables or CSS flexbox (I prefer the latter) are options to achieve this.
Then you can simply render in one pass, and lose all the ref reading, state updating and re-rendering code. Making overall code cleaner and much more efficient.

Sign up to request clarification or add additional context in comments.

3 Comments

I'm actually using flexbox, but not in a row-like manner. I can't really go back and do this at this point as I would have to re-write most of my data structure, which I don't have time for. I do agree, nonetheless, this probably would have been a better solution. However, I don't agree that this is a CSS issue. Even if I set the height and the margin separately after render, I get the same results. The failure is in reading the correct height after render, which is why my question is essentially, I guess, one of questioning how react is reading these DOM values and if there is a better way.
Thanks for the help @wintvelt. It was not a CSS issue, but I did end up getting it to work, albeit in a roundabout and sort of dumb way. You're right about the row grid being a better option, I just don't have time to re-write my data structures now. This was a request from a designer after I had written most of the app. ://
Maybe I did not explain it correctly. The issue is not with how react reads values, but which css values to read. I have updated my answer to clarify. Good to know that you got it work anyway.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.