Shrink wrapping a div to some text is pretty straightforward. But if the text wraps to a second line (or more) due to a max-width (as an example) then the size of the DIV does not shrink to the newly wrapped text. It is still expanded to the break point (the max-width value in this case), causing a fair amount of margin on the right side of the DIV. This is problematic when wanting to center this DIV so that the wrapped text appears centered. It will not because the DIV does not shrink to multiple lines of text that wrap. One solution is to use justified text, but that isn't always practical and the results can be hideous with large gaps between words.
I understand there's no solution to shrink the DIV to wrapped text in pure CSS. So my question is, how would one achieve this with Javascript?
This jsfiddle illustrates it: jsfiddle. The two words just barely wrap due to the max-width, yet the DIV does not then shrink to the newly wrapped text, leaving a nasty right-hand margin. I'd like to eliminate this and have the DIV resize to the wrapped text presumably using Javascript (since I don't believe a solution exists in pure CSS).
.shrunken {text-align: left; display: inline-block; font-size: 24px; background-color: #ddd; max-width: 130px;}
<div class="shrunken">Shrink Shrink</div>
It's not the prettiest solution but it should do the trick. The logic is to count the length of each word and use that to work out what the longest line is that will fit before being forced to wrap; then apply that width to the div. Fiddle here: http://jsfiddle.net/uS6cf/50/
Sample html...
<div class="wrapper">
<div class="shrunken">testing testing</div>
</div>
<div class="wrapper">
<div class="shrunken fixed">testing testing</div>
</div>
<div class="wrapper">
<div class="shrunken">testing</div>
</div>
<div class="wrapper">
<div class="shrunken fixed">testing</div>
</div>
<div class="wrapper">
<div class="shrunken" >testing 123 testing </div>
</div>
<div class="wrapper">
<div class="shrunken fixed" >testing 123 testing </div>
</div>
And the javacript (relying on jQuery)
$.fn.fixWidth = function () {
$(this).each(function () {
var el = $(this);
// This function gets the length of some text
// by adding a span to the container then getting it's length.
var getLength = function (txt) {
var span = new $("<span />");
if (txt == ' ')
span.html(' ');
else
span.text(txt);
el.append(span);
var len = span.width();
span.remove();
return len;
};
var words = el.text().split(' ');
var lengthOfSpace = getLength(' ');
var lengthOfLine = 0;
var maxElementWidth = el.width();
var maxLineLengthSoFar = 0;
for (var i = 0; i < words.length; i++) {
// Duplicate spaces will create empty entries.
if (words[i] == '')
continue;
// Get the length of the current word
var curWord = getLength(words[i]);
// Determine if adding this word to the current line will make it break
if ((lengthOfLine + (i == 0 ? 0 : lengthOfSpace) + curWord) > maxElementWidth) {
// If it will, see if the line we've built is the longest so far
if (lengthOfLine > maxLineLengthSoFar) {
maxLineLengthSoFar = lengthOfLine;
lengthOfLine = 0;
}
}
else // No break yet, keep building the line
lengthOfLine += (i == 0 ? 0 : lengthOfSpace) + curWord;
}
// If there are no line breaks maxLineLengthSoFar will be 0 still.
// In this case we don't actually need to set the width as the container
// will already be as small as possible.
if (maxLineLengthSoFar != 0)
el.css({ width: maxLineLengthSoFar + "px" });
});
};
$(function () {
$(".fixed").fixWidth();
});
I little late, but I think this CSS code can be useful for other users with the same problem:
div {
width: -moz-min-content;
width: -webkit-min-content;
width: min-content;
}
const range = document.createRange();
const p = document.getElementById('good');
const text = p.childNodes[0];
range.setStartBefore(text);
range.setEndAfter(text);
const clientRect = range.getBoundingClientRect();
p.style.width = `${clientRect.width}px`;
p {
max-width: 250px;
padding: 10px;
background-color: #eee;
border: 1px solid #aaa;
}
#bad {
background-color: #fbb;
}
<p id="bad">This box has a max width but also_too_much_padding.</p>
<p id="good">This box has a max width and the_right_amount_of_padding.</p>
I guess this is what you are thinking about, it can be done in css:
div {
border: black solid thin;
max-width: 100px;
overflow: auto;
}
You can see it here: http://jsfiddle.net/5epS4/
Try this:
https://jsfiddle.net/9snc5gfx/1/
.shrunken {
width: min-content;
word-break: normal;
}
Related
I want to know if it's possible to change the text size and color incrementally on the same line, like this:
I want to use CSS only if possible. Any other solution, that at least doesn't force me to put each letter in its own span, is welcome, too.
body {
font-family:monospace;
}
<span style="font-size:50px;">L</span><span style="font-size:45px;opacity:0.7">o</span><span style="font-size:38px;opacity:0.5">r</span>...
What about some transformation and gradient without all these markup:
body {
perspective: 250px;
perspective-origin: bottom;
}
div {
font-size: 70px;
background: linear-gradient(to right, black,rgba(0,0,0,0.3),rgba(0,0,0,0.2));
display: inline-block;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
transform: rotateY(70deg);
transform-origin: left;
}
<div>
Lorem Ipsum Lorem
</div>
That really depends on your HTML markup. You can't do this with plain text "Lorem ipsum", but you can do it if each letter is wrapped in its own separate element pretty simply:
body > span {
font-size: 72px;
}
span > span {
font-size: 85%;
opacity: 0.8;
}
<span>
L<span>
o<span>
r<span>
e<span>
m <span>
i<span>
p<span>
s<span>
u<span>
m
</span>
</span>
</span>
</span>
</span>
</span>
</span>
</span>
</span>
</span>
You likely won't be able to do this without modifying your existing markup or introducing some JavaScript to do this for you, however.
As for the colour, you can change the opacity of each letter with this approach (as per the above example), but I'm not sure if this is possible as easily without having to apply styling to each letter individually.
Using James Donnelly answer with a bit of JS:
format = ([head, ...tail]) => {
if(tail.length == 0)
return "<span>" + head + "</span>";
return "<span>" + head + format(tail) + "</span>";
}
var el = document.querySelector(".test");
el.innerHTML = format(el.innerText)
.test > span {
font-size: 72px;
}
span > span {
font-size: 85%;
opacity: 0.8;
}
<div class="test">
Lorem ipsum
</div>
inspired from #James Donnelly's answer
this solution more dynamic , spans will be generated using javascript
checkout the code
document.addEventListener('DOMContentLoaded', function()
{
var fooDiv = document.getElementsByClassName("foo")[0];
var text = fooDiv.innerHTML.trim();
var textToSpans = "";
var textLength = text.length;
for(var i=0;i<textLength;i++){
textToSpans += "<span>" + text[i];
}
for(i=0;i<textLength;i++){
textToSpans += "</span>";
}
fooDiv.innerHTML = textToSpans;
//change the class so if this code run again this div will not effected
fooDiv.className = "bar";
}, false);
.bar > span {
font-size: 72px;
}
span > span {
font-size: 85%;
opacity: 0.8;
}
<div class="foo">
Lorem ips
</div>
var GradientLetters = class {
constructor(id, fontSizeStep) {
this.element = document.getElementById(id);
this.fontSizeStep = fontSizeStep;
this.letters = document.getElementById(id).innerText.split("");
document.getElementById(id).innerText = "";
this.makeGradient();
}
makeGradient() {
this.letters.forEach((currentValue, index) => {
let span = document.createElement("SPAN");
span.innerText = currentValue;
span.style.color = "rgba(0, 0, 0, " + (1 / index) + ")";
span.style.fontSize = 60 - (index * 2) + "px";
this.element.appendChild(span);
});
}
}
let gradientLetters = new GradientLetters("gradient-letters", 10);
p {
font-family: Arial;
}
<p id="gradient-letters">Lorem ip</p>
Impossible. Without wrapping the letters inside HTML tags simply You can't change CSS properties of the single letters.
The only thing that I've found is the ::first-letter selector, for the first letter, not the following.
Explanation in the CSS context
CSS font-size Property and CSS color Property are properties that you can define in the context of selectors.
Selectors are patterns that match against elements in a tree, and as
such form one of several technologies that can be used to select nodes
in a document.
So, defining CSS properties without pointing to elements (in this case, HTML element) seems to be not possible.
Using JavaScript
Using JavaScript is possible to split a string (In this case the innerText of an HTML Element like a paragraph) in letters, wrapping those letters in HTML Elements and then, style those Elements with CSS in several ways. In this case should be better to set CSS programmatically.
Pretty simple problem, but I can't find a solution. This plugin claims to do it, but I can't get it to work on my site at all, not as a called script, not inline, nothing. So, I have two columns of divs, the ones on one side larger than the other. I have set it up so the second column container will match the height of the first (which is determined elsewhere and thus varies) and set it to overflow:hidden, but what I want to do do is to remove the overflowing divs entirely so it always ends on the last complete div. Here's the fiddle: https://jsfiddle.net/bw2v39ru/2/
This is the JS to equalize the heights $('.row2').css('height', $('.row1').height()+'px');
In that example, only two of he block2 spans should be visible and the overflowing ones removed completely instead of leaving half a block.
Try this: https://jsfiddle.net/bw2v39ru/9/
Besides the code below - you will have to e.g. insert a <br style="clear:both;" /> in the parent DIV since the children has float: left
$('.row2').css('height', $('.row1').height());
var maxHeight = $("#main").outerHeight();
$("#main span").each(function() {
var elm = $(this);
if (elm.offset().top + elm.height() > maxHeight)
elm.remove();
});
as promised, here is my answer. Custom build jsfiddle from pure JavaScript.
https://jsfiddle.net/www139/vjgnsrpg/
Here is a code snippit for you. It assumes that all of your block2 elements have a fixed height. Also I changed the .row1 and .row2 classes to ids to make the solution easier to create. Feel free to change it back but remember to use document.getElementsByClassName('class')[i] instead.
//make sure you execute this script onload inside a jquery document ready or window.onload
//get the rendered height of both rows
//enter margin for blocks here
//this also assumes that the height of your block1 and block2 elements are fixed
var margin = 5;
var rowOneHeight = document.getElementById('row1').offsetHeight;
//get height of block2 element including vertical margin (multiplied twice)
var blockTwoHeight = document.getElementById('row2').getElementsByClassName('block2')[0].offsetHeight + 2 * margin;
var howManyBlocksCanFit = Math.floor(rowOneHeight / blockTwoHeight);
var numberOfBlocks = document.getElementById('row2').getElementsByClassName('block2').length;
for (var i = 0; i != numberOfBlocks - howManyBlocksCanFit; i++) {
document.getElementById('row2').removeChild(document.getElementById('row2').lastElementChild);
}
#main {
width: 240px;
}
#row1 {
float: left;
}
#row2 {
float: right;
overflow: hidden;
}
.block1 {
display: block;
margin: 5px;
width: 100px;
height: 50px;
border: 1px solid red;
}
.block2 {
display: block;
margin: 5px;
width: 100px;
height: 100px;
border: 1px solid red;
}
<div id="main">
<div id="row1">
<span class="block1"></span>
<span class="block1"></span>
<span class="block1"></span>
<span class="block1"></span>
<span class="block1"></span>
</div>
<div id="row2">
<span class="block2"></span>
<span class="block2"></span>
<span class="block2"></span>
<span class="block2"></span>
<span class="block2"></span>
</div>
</div>
Hope this helps you, please tell me if there was something I didn't understand in your question to improve my answer.
I programmed it for you, this works after your existing JS code line:
var row2 = $('div.row2'),
block2elements = row2.children('span.block2');
// Function to use also for other situations
function calculateElementsHeight(elements) {
var height = 0;
$.each(elements, function(i, elementRaw ){
height += $(elementRaw).height();
})
return height;
}
for(var i = 0; block2elements.length > i; i++) {
block2elements = row2.children('span.block2'); // Get new state of the block2 elements
if(row2.height() < calculateElementsHeight(block2elements)) {
block2elements.last().remove();
}
}
When an element contains inline-blocks which contain padding it doesn't get included in the width calculations of the element.
Essentially the same issue as jQuery outerWidth on a parent element which has child elements with padding.
This page should have text that lines up along the right side of the green box,
however the text will always grow larger than it's container, because width never includes the padding of any of it's children.
Is there a way to find the width of an element correctly without manually enumerating all child elements and re-adding the padding of each child? Same results when using .css('width'), .width() or .outerWidth().
<html>
<head>
<script src="http://code.jquery.com/jquery-2.1.0.min.js"></script>
<script>
jQuery(document).ready(function() {
var e = jQuery('#BLAH');
var pw = e.parent().width();
e.css('font-size','1px');
if (e.outerWidth() < pw) {
while ( e.outerWidth() < pw) {
alert('width ' + e.outerWidth() + ' < ' + pw);
e.css('font-size','+=1px');
}
e.css('font-size','-=1px');
}
});
</script>
<style>
#BLAH {
background-color: red;
white-space: nowrap;
}
.BLAH {
//padding: 0 10%;
background-color: blue;
display: inline;
}
</style>
</head>
<body>
<div style="background-color: green; width: 50%; height: 50%">
<div id="BLAH" style="display: inline-block;">
<div class="BLAH">BLAH</div>
<div class="BLAH">BLAH</div>
<div class="BLAH">BLAH</div>
<div class="BLAH">BLAH</div>
</div>
</div>
</body>
</html>
As the issue only occurs with percentage padding, you can instead use fixed pixel padding and increase that along with your font-size in the javascript. Something like this:
jQuery(document).ready(function () {
var e = jQuery('#BLAH');
var pw = e.parent().width();
e.css('font-size', '1px');
if (e.outerWidth() < pw) {
while (e.outerWidth() < pw) {
console.log('width ' + e.outerWidth() + ' < ' + pw);
e.css('font-size', '+=1px');
jQuery(".BLAH").css({
'padding-right': '+=1px',
'padding-left': '+=1px'
});
}
e.css('font-size', '-=1px');
jQuery(".BLAH").css({
'padding-right': '-=1px',
'padding-left': '-=1px'
});
}
});
http://jsfiddle.net/5Gufk/
If you wanted to get more sophisticated with it, you could calculate the padding as a percentage of the parent element's previous width and apply that rather than just increasing by one, or any other formula.
As for why it works this way, I was unable to find the part of the CSS spec that defines this behavior. However, it is important to understand that a percentage padding is based on the width of the containing block. Consider how it would work if you had 6 elements, all with 10% padding on both sides. That would be 120% padding, how could that even be possible for the padding of the elements to be 120% of the width of the parent element and still fit inside the parent?
I have a bunch of divs inside a container. The position of the content divs is relative, because I want them to appear one below the other and their height is unknown.
These divs are created dynamically (appendchild) inside the container div. Now, each div appears on the end (bottom) of the stack but my requirement is that the divs have a "newest first" option too, that is, each new div appears on top, not on bottom of the content divs (if the user selects the "newest first" in the settings).
html:
<div class="container">
<div id="div1" class="content">aaa<br>aaa</div>
<div id="div2" class="content">bbb<br><br>bbb</div>
<div id="div3" class="content">ccc</div>
<div id="div4" class="content">ddd</div>
</div>
css:
.container {
position: absolute;
top: 10px;
left: 10px;
right: 10px;
bottom: 10px;
border: 1px solid red;
}
.content {
position: relative;
top: 0px;
left: 5px;
width: 200px;
height: auto;
border: 1px solid blue;
margin: 3px;
}
http://jsfiddle.net/jk559/1/
so I'd like the end-user visible order to be: div4, div3, div2, div1.
How can I achieve this? (css/js)
preferrably no jquery.
thanks in advice!
Pure css solution:
Use flexbox to achieve this.
.container {
display:flex;
flex-direction:column-reverse;
justify-content: flex-end;
align-content: flex-end;
}
Updated fiddle here.
Read more information here.
try this
theParent = document.getElementById("theParent");
theKid = document.createElement("div");
theKid.setAttribute("id","div5");
theKid.setAttribute("class","content");
theKid.innerHTML = 'eee';
// append theKid to the end of theParent
theParent.appendChild(theKid);
// prepend theKid to the beginning of theParent
theParent.insertBefore(theKid, theParent.firstChild);
Demo Fiddle http://jsfiddle.net/jk559/4/
You can easily do it with JQuery with the following function.
$('.container > div').each(function() {
$(this).prependTo(this.parentNode);
});
UPDATED FIDDLE
As you mentioned in the question, I will try to attain the expected output with the pure javascript.
You can insert content in the beginning simply using .prepend() .
$(".container").prepend("<div id='div5' class='content'>eee</div>");
Demo
JS FIDDLE UPDATED DEMO
Use prepend() to add as first child of an element
/* $( ".container" ).prepend( "Your div with id here" ); */
/* Example */
$( ".container" ).prepend( "<div id='div5' class='content' >div5 on top </div>" );
Take a look at this answer about reordering dom items.
Basically, you have to maintain a state that decides the ordering. When you insert items (see insertItem below) you append or prepend based on the state. When the user selects the newest first option (see newFirst below), you first reverse the dom elements and then flip the state so that subsequent insert happen at the right place.
var newFirst = false;
var list = document.getElementById('my-list');
function newFirst() {
var items = list.childNodes;
var itemsArr = [];
for (var i in items) {
if (items[i].nodeType == 1) { // get rid of the whitespace text nodes
itemsArr.push(items[i]);
}
}
itemsArr.reverse();
for (i = 0; i < itemsArr.length; ++i) {
list.appendChild(itemsArr[i]);
}
newFirst = !newFirst;
}
function insertItem(content) {
var item = document.createElement("div");
item.setAttribute("class","content");
item.innerHTML = content;
if(newFirst) {
list.insertBefore(item, list.firstChild);
} else {
list.appendChild(item);
}
}
try this :
$("div[id*=div]").sort(function(a,b){
if(a.id > b.id) {
return -1;
} else {
return 1;
}
}).each(function() {
var elem = $(this);
$(".container").append(elem);
});
this will sort your divs inside container like this : div4, div3, div2, div1
if you want change the order to : div1, div2, div3, div4 just change if(a.id > b.id) to if(a.id < b.id)
you can add a link called change order then call this code when you click on it
I got the following HTML page with just a couple of DIV-elements. These elements are generated with random values for the left margin.
Additional Info: The DIV-elements in the HTML file have to stay in the given order.
<html>
<head>
<style type="text/css">
.box{
height: 50px; width: 50px; border: 1px solid black;
text-align: center; margin-bottom: 5px;
}
</style>
<title></title>
</head>
<body>
<div class="box">1</div>
<div class="box" style="margin-left: 30px">2</div>
<div class="box" style="margin-left: 90px">3</div>
<div class="box" style="margin-left: 120px">4</div>
<div class="box" style="margin-left: 240px">5</div>
</body>
</html>
The resulting page looks like this:
Is there a way, to align the DIV-elemets to the top of the page with pure CSS?
DIV-elements should stack up, if they aren't completely beside the preceding DIV. That is why element 2 and 4 are in the second row.
Since there is no 'float: top;' command, I have no idea how to achieve the following layout:
Since my problem is still not clear, here is another example to clarify, that the layout should work with random generated DIV-elements:
If the element does not fit beside the preceding element, it should stack up. If it fits, it should align to the top.
If there is no way with pure CSS, a JS workaround would also be possible.
You can call this javascript function once the page is loaded (<body onload="float_top()">) :
var top = 5; // top value for next row
var margin = 5; // space between rows
var rows = []; // list of rows
function float_top() {
for (var c = 0; c < document.body.children.length; c++) {
var ok = false;
var child = document.body.children[c];
var cr = child.getBoundingClientRect();
for (var i = 0; i < rows.length; i++) {
if (cr.left > rows[i].right) {
rows[i].right = cr.right;
child.style.top = rows[i].top + "px";
ok = true;
break;
}
if (cr.right < rows[i].left) {
rows[i].left = cr.left;
child.style.top = rows[i].top + "px";
ok = true;
break;
}
}
if (!ok) {
// add new row
rows.push({
top: top,
right: cr.right,
left: cr.left
});
child.style.top = top + "px";
top = child.getBoundingClientRect().bottom + margin;
}
}
}
It iterates over the divs, keeps track of the space used by each div and adds "rows" when there's no more space to put a div in the existing rows.
See this jsFiddle.
Note:
for this to work, the position of the divs must be set to absolute
the divs are assumed to be the same height. You'll need some tweaking if it's not the case.
You can either apply a float: left to the .box class or set its display value to inline-block, unless there's a reason elements 2 and 4 in your diagram have to be below 1, 3, and 5.
There's no float: top because it's not strictly required. divs are block elements, which means that in the normal flow of the document, they appear beneath one another, according to their placement in your HTML. There's a hybrid display value that I mentioned above, inline-block, which has block-like qualities but displays the elements in a horizontal fashion.
If you require that certain elements on your page "float" up or down (in the every day sense of the word), you might try applying a margin-top to the elements that need be be lower?
You have to use float:left; and re-arrange divs
<table><tr>
<td><div class="box">1</div></td>
<td><div class="box" style="margin-left: 30px;">2</div></td>
<td><div class="box" style="margin-left: 90px">4</div></td>
<td><div class="box" style="margin-left: 120px">3</div></td>
<td><div class="box" style="margin-left: 240px">5</div></td>
</tr></table>
<style>
table{width:100%;}
table td{
height:120px;
}
table td:nth-child(1n){
vertical-align:top;
}
table td:nth-child(2n){
vertical-align:bottom;
}
.box{
height: 50px;
width: 50px;
border: 1px solid black;
text-align: center;
margin-bottom: 5px;
}
</style>
Please check jsfiddle demo