I have a bunch of divs that have a content div inside of them. In the content div are 3 elements, an h1, a p and a span, all left-aligned. What I want to happen is the following:
The content div should be vertically and horizontally centered
The content div should be exactly as wide as the text in the h1 or the text in the span (whichever is longer), if above the max-width these should wrap
The p should be 75% as wide as the content div but not have an impact on the content div's size (effectively being 75% as wide as the h1 or span, whichever is longer)
However I'm running into the following problems:
Problem 1: Having a long p element causes the content div to expand to its max-width no matter the size of the h1 or span. I've tried using absolute positioning to fix this but it disrupts the vertical centering of the div
Problem 2: Having a long h1 element leaves a gap where the word breaks over 2 lines making the content div not appear centered
See the code snippet below to clarify what I'm after and what's going wrong, the borders are just to help visualise what's happening.
Has anyone got an idea of how this is possible? I would like to stick to CSS as these need to be responsive, although if there is a simple JS/jQuery solution it would be considered.
EDIT: To clarify the visual effect I am after here's a run-down of why the examples are good or bad. I've also added the ability to remove borders to demonstrate what I mean by something being visually centred:
1) Good: Content div fits to width of h1, looks centered without the borders as equal space to left and right of h1
2) Good: Content div fits to width of span as it's longer than the h1, looks centered without the borders as equal space to left and right of span
Problem 1:
3) Bad: p is expanding the width of the content div, looks shifted to the left without borders as more space on right than left. If the p did not expand the div and stayed at 75% of the width this would not happen
4) Improvement on 3 but still bad: Potential fix found in various SO questions showing absolute positioning stops the p expanding the content div, but now that it is not part of the flow it messes up the vertical centering
Problem 2:
5) Bad: The problem here is the h1 element, because it is now longer than the max-width it splits into 2 lines. But the extra space between the end of the first line and the max-width of the div is kept so when removing borders it doesn't look centered because there is more space to the right than the left of the h1
6) Fixes 5 but not a solution: Manually breaking the line (using a <br>) achieves the look I need, because the h1 isn't expanded to the max-width so looks centered without the borders. This isn't feasible in the real application though because the divs can vary in width
Alternative JSFiddle Link
function toggleBorders() {
$('h1, p, span').toggleClass('bordered');
$('.content').toggleClass('content-bordered');
}
.box-holder {
display: flex;
flex-flow: row wrap;
}
.box {
flex: 0 0 380px;
display: flex;
align-items: center;
justify-content: center;
height: 350px;
border: 1px solid blue;
}
.content {
max-width: 80%;
position: relative;
}
.content-bordered {
border: 1px solid red;
}
.bordered {
border: 1px solid green;
}
p {
width: 75%;
}
.abs {
position: absolute;
}
button {
position: fixed;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button onclick="toggleBorders()">Toggle Borders</button>
<div class="box-holder">
<div class="box">
<div class="content">
<h1>1. Example Title</h1>
<p>Good Example</p>
<span>Example link to the article</span>
</div>
</div>
<div class="box">
<div class="content">
<h1>2. Title</h1>
<p>Min Width Good Example</p>
<span>Example link to the article</span>
</div>
</div>
<div class="box">
<div class="content">
<h1>3. Example Title</h1>
<p>But when the description gets too long then it expands the content div</p>
<span>Example link to the article</span>
</div>
</div>
<div class="box">
<div class="content">
<h1>4. Example Title</h1>
<p class="abs">Setting absolute position avoids expansion but messes up the vertical layout</p>
<span class="abs">Example link to the article</span>
</div>
</div>
<div class="box">
<div class="content">
<h1>5. Also Long Titles Leave White Space</h1>
<p>This doesn't look centered, see 6</p>
<span>Example link to the article</span>
</div>
</div>
<div class="box">
<div class="content">
<h1>6. Also Long Titles<br>Leave White Space</h1>
<p>Manually breaking lines fixes this</p>
<span>Example link to the article</span>
</div>
</div>
</div>
So the answer to both of these questions, it seems, is that you cannot do this without javascript. The reason being that the CSS box model just does not work in this way.
In order to solve just the first problem you need to use absolute positioning like I tried but then use javascript to create a space for the element using a margin on the h1, something like this:
$(document).ready(function() {
function alignDescriptions() {
var pmargin = 10 * 2;
$('.abs').each(function() {
var pheight = $(this).height();
$(this).css('bottom', pmargin);
$(this).siblings('h1').css('margin-bottom', pheight + pmargin);
});
}
});
That solves the vertical centering issue when using absolute so problem 1 is fixed.
To solve the second problem, the following answer provides one solution: https://stackoverflow.com/a/33246364/7542390
I believe simply using this but also using the width of the span as a minimum would probably solve both problems as I would be literally forcing the width to the correct size so having a 75% width p element wouldn't be a problem.
It's a shame that this kind of functionality isn't in the CSS spec.
EDIT: As suspected, an adaption of the second option actually removes the need for absolute positioning of the p element. Here's the jQuery code that worked for my actual case:
$('h1').each(function() {
// references to elements
var hElem = $(this);
var pElem = hElem.siblings('p');
var sElem = hElem.siblings('span');
// store starting values
var sWidth = sElem.width();
var hHeight = hElem.height();
var hWidth = hElem.width();
// reduce width until less than span width
// or until height of h1 changes
for (var testWidth = hWidth - 1; testWidth > 0; testWidth--) {
if (testWidth <= sWidth) {
testWidth = sWidth - 1;
break;
}
hElem.width(testWidth);
if (hElem.height() !== hHeight) break;
}
// set h1 width
hElem.width(++testWidth);
// if h1 still overflows (long word) use that instead
if (hElem[0].scrollWidth > hElem.width()) {
testWidth = hElem[0].scrollWidth;
hElem.width(testWidth);
}
// set p element to 75% width
pElem.width(testWidth * 0.75);
});
Your question is very interesting.I have solved your div expansion of the description by setting the max-width as the same as your min-width. Then it cannot expand.
I can't figure out the problem with the white space and title. It looks centred to me.
Here is what I got:
.box-holder {
display: flex;
flex-flow: row wrap;
}
.box {
flex: 1 0 350px;
display: flex;
align-items: center;
justify-content: center;
height: 350px;
border: 1px solid blue;
}
.content {
min-width: 50%;
max-width: 50%;
border: 1px solid red;
}
h1,
p,
span {
border: 1px solid green;
}
p {
width: 75%;
}
.abs {
position: absolute;
}
<div class="box-holder">
<div class="box">
<div class="content">
<h1>1. Example Title</h1>
<p>Good Example</p>
<span>Link</span>
</div>
</div>
<div class="box">
<div class="content">
<h1>2. Title</h1>
<p>Min Width Good Example</p>
<span>Link</span>
</div>
</div>
<div class="box">
<div class="content">
<h1>3. Example Title</h1>
<p>When the description gets too long then it DOESN'T expand the content div</p>
<span>Link</span>
</div>
</div>
<div class="box">
<div class="content">
<h1>4. Example Title Size</h1>
<p class="abs">Setting absolute position avoids expansion but messes up the vertical layout</p>
<span class="abs">Link</span>
</div>
</div>
<div class="box">
<div class="content">
<h1>5. Also Long Titles Leave White Space</h1>
<p>This does look centered</p>
<span>Link</span>
</div>
</div>
</div>
I hope it helps and this is what you're looking for.
EDIT
Solving 5 with text-align: justify;
.box-holder {
display: flex;
flex-flow: row wrap;
}
.box {
flex: 1 0 350px;
display: flex;
align-items: center;
justify-content: center;
height: 350px;
border: 1px solid blue;
}
.content {
min-width: 50%;
max-width: 50%;
border: 1px solid red;
}
h1,
p,
span {
border: 1px solid green;
}
p {
width: 75%;
}
.abs {
position: absolute;
}
/*NEW CSS:*/
h1 {
text-align: justify;
}
<div class="box-holder">
<div class="box">
<div class="content">
<h1>1. Example Title</h1>
<p>Good Example</p>
<span>Link</span>
</div>
</div>
<div class="box">
<div class="content">
<h1>2. Title</h1>
<p>Min Width Good Example</p>
<span>Link</span>
</div>
</div>
<div class="box">
<div class="content">
<h1>3. Example Title</h1>
<p>When the description gets too long then it DOESN'T expand the content div</p>
<span>Link</span>
</div>
</div>
<div class="box">
<div class="content">
<h1>4. Example Title Size</h1>
<p class="abs">Setting absolute position avoids expansion but messes up the vertical layout</p>
<span class="abs">Link</span>
</div>
</div>
<div class="box">
<div class="content">
<h1>5. Also Long Don't Titles Leave White Space</h1>
<p>This does look centered</p>
<span>Link</span>
</div>
</div>
</div>
Related
I am trying to create rows in a DIV where there are three pieces of information to display. A left align text, a right align text, and repeat character "." between the two
I tried this using the HTML below. But it is not working with a div for each piece.
<div style="margin-bottom:5px"><!-- row 1 -->
<div style="float:left;">Left</div> <!-- left column -->
<div style="display: inline-block;">...</div> <!-- center column -->
<div style="float:right;">Right</div> <!-- center column -->
</div><!-- row 1 -->
I believe it most likely has to be done using just one div displaying one piece of text/string where the "." character gets repeated between the other two pieces of text. I cant figure it out so that the string changes with the DIV size - in order to keep it responsive.
You could achieve that rather simply with flexbox and a dotted border.
.row { display: flex; }
.center {
flex-grow: 1;
border-bottom: 2px dotted black;
margin: 0 4px 4px;
}
<div class="row">
<div class="left">Left</div>
<div class="center"></div>
<div class="right">Right</div>
</div>
<div class="row">
<div class="left">This is some longer left text</div>
<div class="center"></div>
<div class="right">Right</div>
</div>
<div class="row">
<div class="left">Left</div>
<div class="center"></div>
<div class="right">This is some longer right text</div>
</div>
Make parent display: flex so that children align side-by-side.
Add flex-grow: 1 to the center so that it fills empty space
Add border-bottom: 2px dotted black to achieve your dotted line
Add some margins to position and pad the dotted line properly
You could do something like this :
<div class="row>
<div class="left"> Left </div>
<div class="middle"></div>
<div class="right"> Right </div>
</div>
And use flexbox to set the sizes:
.row { display: flex; }
.left , .right { flex: 0 0 auto; } /* will not shrink or grow, with their default width set to auto */
.middle { flex: 1 1 0; } /* will shrink/grow to fill all available space, with a default width of 0 */
Now, you could apply something like:
.middle {
position: relative;
border-bottom: 1px dotted currentColor;
transform: translateY(-.4em); /* change to reposition vertically */
}
You can also add spacing by :
.left { padding-right: 1em; }
.right { padding-left: 1em; }
Need a little bit help here. Thanks :)
I am struggling with how to make the outer div wrap the inner div and expand upwards along with the inner content editable div.
The inner div should expand from bottom to top and the outer div should wrap it (green color should wrap the red) and expand along with it.
Note: press SHIFT+ENTER in the red div to make it expand upwards.
I have an example in the following codepen
<div style="background-color:green;">
Test Test
<div id="example" contenteditable style="background-color:red; position: absolute; bottom: 0px">
Test Test
</div>
</div>
You can use flexbox:
.outer {
background: green;
display: flex;
flex-direction: column;
/* Some minimal width */
min-height: 50vh;
}
.inner {
background: red;
margin-top: auto;
}
<div class="outer">
Test Test
<div id="example" class="inner" contenteditable>
Test Test
</div>
</div>
Both of them must be absolute and wrappers height must be 100%.
<div style="background-color:green; height: 100%; position: absolute;">
wrapper
<div id="example" style="border: 1px solid red; position: absolute; bottom: 0;">
inner
</div>
</div>
I have inherited somebody else's problem. The HTML is all DIVs with floats, displays and positioning tweaks. The one thing I cannot change is the structured of the HTML DIVs. Nor do I wish to add any new javascript libraries. But I can add all the CSS I need to the existing DIVs.
Currently 3 DIVs are embedded as:
<DIV id="firstrow"> 1 </DIV>
<DIV id="secondrow">
<DIV> 2 </DIV>
<DIV> 3 </DIV>
</DIV>
Take a look at the graphic below. The problem with this is that as DIV1 grows down, the DIV3 gets bumped down. I wish to keep DIV3 fully justified from the top to bottom (as if STRETCH).
Without getting into how the current code combines DISPLAYS, FLOATS, and POSITIONING -- I think I need to erase all the CSS and replace with some FLEXBOX. But I cannot seem to get the right combination of FLEX properties to make DIV3 behave to stretch (instead of getting bumped down).
Fortunately, this only has to work for Chrome on Desktop (no mobile nor other browsers).
There you go IF width of div 3 is known and fixed value:
https://codepen.io/AugustinF/pen/qYBpmR
.wrapper {
position: relative;
border: 2px solid black;
}
#firstrow {
height: 100px;
margin-right: 200px;
background: green;
}
#secondrow {
}
#div2 {
float:left;
background: blue;
}
#div3 {
width: 200px;
position: absolute;
top:0;
right:0;
height: 100%;
background: red;
}
.clearfix:after {
content: ".";
visibility: hidden;
display: block;
height: 0;
clear: both;
}
HTML:
<div class="wrapper clearfix">
<DIV id="firstrow"> 1 </DIV>
<DIV id="secondrow clearfix">
<DIV id="div2"> 2 </DIV>
<DIV id="div3"> 3 </DIV>
</DIV>
</div>
Using #Pete solution you can modify the HTML structure using javascript by placing this code at the end of the body tag:
<script>
document.getElementById('firstrow').appendChild(
document.getElementById('div2')
);
</script>
I can align two div's by simply setting their display to inline-block and using same line-heights like shown in the below:
However, what I want is that aligning two nested divs according to the baseline of the inner divs like this:
I can achieve this also using jquery by taking the longest heading's height and set all the headings' height to this value.
var fitSizes = function () {
var h = 0;
$('h1').each( function(){
if(h < $(this).outerHeight()) h = $(this).outerHeight();
// select biggest height
});
$('h1').each( function(){
$(this).outerHeight(h);
// set all h1 heights to the biggest height
});
};
fitSizes();
But for some reasons I don't want to use js or jquery. Is there any "CSS only" way to achieve something like that?
Any solution I can think of here seems hacky, as are usually problems of this nature. There is ALWAYS a scenario where they will break. A programmatic approach, however bloated and ugly, will definitely give you exactly what you want.
I'm going to make an assumption that both the header and the content are of varying lengths and there may be more than just 2 on page either on a single line or multiple.
TOP DOWN APPROACH // fixed header height
There's no reason why your approach above won't work for a nested div. I would wrap the h1 if you're applying styles to it though. Setting a line-height on a h1 if it breaks across lines will cause each line in the multiline to have that line height. Wrap the header in a div and give that a static height, that way if the has styles such as a background won't be affected by the "margin".
<style>
.wrapper {
height: 2.5rem;
line-height: 2.5rem;
text-align: bottom;
// flex approach works too
}
h1 {
line-height: 1rem;
}
</style>
...
<div class="container">
<div class="wrapper">
<h1>title</h1>
</div>
<div class="content">
<p>lorem ipsum....</p>
</div>
</div>
"BOTTOM UP" APPROACH // fixed content
This would work better if the "anchor" for these components is the bottom of the page. If your content varies in length you could fix the height of the container and content.
<style>
.container {
text-align: bottom;
}
h1 {
line-height: 1rem;
}
.content {
height: 15rem;
overflow: elipsis;
}
</style>
...
<div class="container">
<h1>title</h1>
<div class="content">
<p>lorem ipsum....</p>
</div>
</div>
You could do it with flexbox, you could set make the outer div's siblings by making a container around them and do something like this:
.container {
display: flex;
align-items: flex-end;
}
If this does not fulfill your needs you could also try and see if align-items: baseline; fixes it. Just have a look at flexbox.
What You are looking for using flexbox.
.root {
display: flex;
height: 300px;
border: 2px solid black;
}
.container {
display: flex;
/* position at bottom of container */
margin-top: auto;
/* spread inside container */
flex-grow: 1;
/* align items in row and center it verticaly */
flex-direction: row;
justify-content: center;
}
.column {
display: flex;
margin: auto 5px 5px;
padding: 20px;
/* spread inside container */
flex-grow: 1;
/* align items in column, and position content at the bottom */
flex-direction: column;
justify-content: flex-end;
border: 2px solid blue;
}
.row {
margin: 5px;
padding: 10px;
border: 1px solid red;
text-align: center;
}
<div class="root">
<div class="container">
<div class="column">
<span class="row">row one</span>
</div>
<div class="column">
<span class="row">row one</span>
<span class="row">row two</span>
</div>
</div>
</div>
Flexbox is something up and coming that would be really useful to use. It's only growing in popularity.
This can answer many different problems. Such as your justification issue.
Here's a simple fiddle with little code that shows the answer to your problem: https://jsfiddle.net/hkLk53c6/
HTML:
<div class="container">
<div class="flex-item">
Item
</div>
<div class="flex-item">
Item
<br>
Item
</div>
<div class="flex-item">
Item
<br>
Item
<br>
Item
</div>
</div>
CSS:
.container {
display:flex;
justify-content: space-between;
border: 1px solid #333;
}
.flex-item {
width: 33%;
text-align: center;
background: #120321;
color: #fff;
}
Here's a link to Chris Coyier's explanation about Flexbox Properties:
https://css-tricks.com/snippets/css/a-guide-to-flexbox/
I've personally found it very useful in learning more about flexbox.
You can styled based off of the baseline, you can reverse elements, justify them to be the same height both vertically and horizontally. Etc. It's very exciting because now we can get past some hacky fixes. (Like using JS which trust me, you're not the only one to do so far!)
How can I condition my text to become left-aligned if hits the end of its contained space and has to switch to the next line? Is there something inherent within CSS that can detect it, or does it have to be a JavaScript solution?
Here's the fiddle so you can see how it should behave: https://jsfiddle.net/fj4ddmey/2/
.text.container {
margin: 0 auto;
text-align: center;
width: 350px;
border: 1px solid black;
}
.text.container.two {
text-align: left;
}
<div class="text container">
<p>This is how short sentences should look</p>
</div>
<div class="text container">
<p>This text should be left aligned because it hits a line break</p>
</div>
<div class="text container two">
<p>This is how it should look, but it needs to be a fluid solution</p>
</div>
Use flexbox like that
.container {
display: flex;
justify-content: center;
}
<div class="container">
<p>This is how it should look, but it needs to be a fluid solution</p>
</div>
Here is JSFiddle demo
You can set the <p> as inline block, so that text-align:center on the container will center the <p> tag first, rather than the text. And inline-block has the shrink-to-fit feature, means the width is determined by the content, and never goes beyond the container, with text-align:left, text inside will be left aligned when it wraps.
.container {
width: 350px;
margin: 0 auto;
text-align: center;
outline: 1px solid black;
}
.container p {
display: inline-block;
text-align: left;
outline: 1px dotted red;
}
<div class="container">
<p>This is how short sentences should look</p>
</div>
<div class="container">
<p>This text should be left aligned because it hits a line break</p>
</div>
You can use jQuery to determine the element height, and if its greater than one line, add a class to justify the text appropriately.
Something like this should work:
function countLines(e) {
var elementHeight = e.innerHeight();
var lineHeight = parseInt(e.css('line-height'));
var lines = elementHeight / lineHeight;
return(lines > 1);
}
$('p').each(function() {
if(countLines($(this))) {
$(this).addClass('two'); //class to left justify
}
})
You can see it working here: https://jsfiddle.net/igor_9000/fj4ddmey/1/