This question already has answers here:
Widow/Orphan Control with JavaScript?
(7 answers)
Closed 8 years ago.
A common problem when working with typography in HTML/CSS is something we call "horunge" in Swedish ("widow" in english).
What it is:
Let's say you have a box with a width of 200px and with the text "I love typograpy very much". Now the text breaks and becomes:
I love typography very
much
As a designer I don't want a word bastard (single word / row). If this was a document/PDF etc. I would break the word before very and look like this:
I love typography
very much
which looks much better.
Can I solve this with a CSS rule or with a javascript? The rule should be to never let a word stand empty on a row.
I know it can be solved by adding a <br /> but that's not a solution that works with dynamic widths, feed content, different translations, browser font rendering issues etc.
Update (solution)
I solved my problem with this jquery plugin: http://matthewlein.com/widowfix/
A simple jQuery / regrex solution could look like the following, if you add the class "noWidows" to the tag of any element that contains text you are worried about.
Such as:
<p class="noWidows">This is a very important body of text.</p>
And then use this script:
$('.noWidows').each(function(i,d){
$(d).html( $(d).text().replace(/\s(?=[^\s]*$)/g, " ") )
});
This uses regex to find and replace the last space in the string with a non-breaking character. Which means the last two words will be forced onto the same line. It's a good solution if you have space around the end of the line because this could cause the text to run outside of an element with a fixed width, or if not fixed, cause the element to become larger.
Just wanted to add to this page as it helped me a lot.
If you have (widows) actually should be orphans as widows are single words that land on the next page and not single words on a new line.
Working with postcodes like "N12 5GG" will result in the full postcode being on a new line together but still classed as an orphan so a work around is this. (changed the class to "noWidow2" so you can use both versions.
123 Some_road, Some_town, N12 5GG
$('.noWidows2').each(function(i,d){
var value=" "
$(d).html($(d).text().replace(/\s(?=[^\s]*$)/g, value).replace(/\s(?=[^\s]*$)/g, value));
});
This will result is the last 3 white spaces being on a new line together making the postcode issue work.
End Result
123 Some_road,
Some_town, N12 5GG
I made a little script here, with the help of this function to find line height.
It's just an approach, it may or may not work, didn't have time to test throughly.
As of now, text_element must be a jQuery object.
function avoidBastardWord( text_element )
{
var string = text_element.text();
var parent = text_element.parent();
var parent_width = parent.width();
var parent_height = parent.height();
// determine how many lines the text is split into
var lines = parent_height / getLineHeight(text_element.parent()[0]);
// if the text element width is less than the parent width,
// there may be a widow
if ( text_element.width() < parent_width )
{
// find the last word of the entire text
var last_word = text_element.text().split(' ').pop();
// remove it from our text, creating a temporary string
var temp_string = string.substring( 0, string.length - last_word.length - 1);
// set the new one-word-less text string into our element
text_element.text( temp_string );
// check lines again with this new text with one word less
var new_lines = parent.height() / getLineHeight(text_element.parent()[0]);
// if now there are less lines, it means that word was a widow
if ( new_lines != lines )
{
// separate each word
temp_string = string.split(' ');
// put a space before the second word from the last
// (the one before the widow word)
temp_string[ temp_string.length - 2 ] = '<br>' + temp_string[ temp_string.length - 2 ] ;
// recreate the string again
temp_string = temp_string.join(' ');
// our element html becomes the string
text_element.html( temp_string );
}
else
{
// put back the original text into the element
text_element.text( string );
}
}
}
Different browsers have different font settings. Try to play a little to see the differences. I tested it on IE8 and Opera, modifying the string every time and it seemed to work ok.
I would like to hear some feedback and improve because I think it may come in handy anyway.
Just play with it! :)
There are also CSS widows and orphans properties: see the about.com article.
Not sure about browser support...
EDIT: more information about WebKit implementation here: https://bugs.webkit.org/buglist.cgi?quicksearch=orphans.
Manually, you could replace the space in between with
I've been looking for ways to dynamically add it in. I found a few, but haven't been able to make it work myself.
$('span').each(function() {
var w = this.textContent.split(" ");
if (w.length > 1) {
w[w.length - 2] += " " + w[w.length - 1];
w.pop();
this.innerHTML = (w.join(" "));
}
});
#foo {
width: 124px;
border: 1px solid #ccc;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="foo">
<span class="orphan">hello there I am a string really really long, I wonder how many lines I have</span>
</div>
Related
I'm making a bunch of stacked divs that will expand when moused-over to show an image, but I have a lot of images.
Is there a way in CSS or JS (I don't really know anything about JS) to have each div automatically use the next image in a folder? ex: the images are named map1.jpg, map2.jpg ... map91.jpg. and be able to use the same background-image:url but have something telling it to add 1 to the next image for each new div so I don't have to manually specify 90+ different images.
I hope I was able to explain that well enough. Thanks =)
In CSS this is impossible, cause you can't concatenate the url path for background-image.
in javascript this is pretty simple, using jQuery you can simply load all div you need when body is ready:
// on page load
$(document).ready(function(){
// 10 images to div #image-board
for(var i=0; i<10; i++){
// create a div with image #i
$('#image-board').append('<div><img src="my/collection/folder/Image'+i+'.jpg"></div>');
}
});
don't forget to create in your HTML page a <div id="image-board"></div> where all images will be listed to
I'll try to include lots of detail since you say you're unfamiliar with JavaScript. When you say stacked divs, I assume you mean one inside another. Start with a div with appropriate id and background in your html <div id="div0" style="background-image:url('map0.jpg')"></div> Here's some Javascript using jQuery (a very common javascript library you can include with a script tag) that will add a new div inside your first div with the updated url name.
for (var i = 0; i < number_of_images - 1; i++) {
var oldId = '#div' + i;
var newId = 'div' + (i + 1);
var newUrl = 'map'+(i+1)+'.jpg';
var newdiv = '<div id="' + newId + '" style="background-image:url('+newUrl+')></div>';
$(oldId).append(newdiv);
}
The for loop will go over every image, then a string is created in a series of concatenations that becomes your updated div. The '$' searches for the DOM element with that id, then adds the new div inside it. If by stacked you meant a new div underneath but not contained by the previous div, use .insertAfter instead of .append. Assuming your webpage can reach all of the images, this should work. Also notice I've 0 indexed this (the standard in Javascript) but your question referred to map1 as the first map. If you have already named these maps, you may need to re-index the for loop to 1.
The "obvious" answer is to use the line-height CSS rule, but this code (where I do use that):
private void GenerateSection7()
{
var headerFirstPart = new Label
{
CssClass = "finaff-webform-field-label",
Text = "<h2><strong>Section 7: Submit Information </strong></h2>"
};
headerFirstPart.Style.Add("display", "inline-block");
headerFirstPart.Style.Add("white-space", "pre");
headerFirstPart.Style.Add("line-height", "20%");
var headerSecondPart = new Label
{
CssClass = "finaff-webform-field-label",
Text = " (This payment is subject to post audit review by Financial Affairs)"
};
headerSecondPart.Style.Add("display", "inline-block");
headerFirstPart.Style.Add("line-height", "20%");
this.Controls.Add(headerFirstPart);
this.Controls.Add(headerSecondPart);
var submissionInstructions = new Label
{
CssClass = "finaff-webform-field-label",
Text = "<h4>Submit completed and approved form to Mail stop: FAST/AP Office</h4>"
};
this.Controls.Add(submissionInstructions);
}
...produces this result:
As you can see, the Cumberland gap could fit between these two lines. I started with 80% and kept reducing the percentage, but it makes no difference - 20% is no better than 50% (which was no better than 80%).
How can I cinch up the "play" between the lines?
UPDATE
Spurred on by oriol and quinnuendo, I changed my code to this:
private void GenerateSection7()
{
var headerFirstPart = new Label
{
CssClass = "finaff-webform-field-label",
Text = "<h2><strong>Section 7: Submit Information </strong></h2>"
};
headerFirstPart.Style.Add("display", "inline-block");
headerFirstPart.Style.Add("white-space", "pre");
//headerFirstPart.Style.Add("line-height", "20%");
headerFirstPart.Style.Add("margin", "0");
var headerSecondPart = new Label
{
CssClass = "finaff-webform-field-label",
Text = " (This payment is subject to post audit review by Financial Affairs)"
};
headerSecondPart.Style.Add("display", "inline-block");
//headerSecondPart.Style.Add("line-height", "20%");
headerFirstPart.Style.Add("margin", "0");
this.Controls.Add(headerFirstPart);
this.Controls.Add(headerSecondPart);
var submissionInstructions = new Label
{
CssClass = "finaff-webform-field-label",
Text = "<h4>Submit completed and approved form to Mail stop: FAST/AP Office</h4>"
};
submissionInstructions.Style.Add("margin", "0");
this.Controls.Add(submissionInstructions);
}
...but it still generates exactly the same page (with the Grand Canyon between the last two lines preventing Evel Kneivel from safely traversing the expanse).
Note: I tried both "0" (as shown above) and "0px" as the margin values, and the results are identical either way.
Line height is not the problem here. The problem is the space between the elements, which is controlled by the margin properties.
These tend to be preset to some values that allow for "normal" text flow, so you will have space before and after a heading and around paragraphs for instance.
Headings have various amounts of margin-top and margin-bottom depending on the level of the heading (h2 will have more than an h4). For instance it is "1em" sometimes (the width of a capital M in the current font).
So you will want to reduce these, or set them to 0 directly and experiment from there. The margins need to be set for the actual headings (h4, h2, or whatever) not for the containing element, they will not inherit it. For initial experiments you can try to just apply everything directly in the code, something like <h4 style='margin:0px'> ..... For a proper solution you would want to define actual CSS sheets for the particular case with introducing classes into the heading or in some surrounding div or an analogue.
In some cases you may also need to check out the padding property to fully control the spacings.
The problem is not the line-height but the vertical margins between elements. Also the fact that the headerSecondPart is untag.
See example for the solution.
trying to set up css doing the following:
Imagine we have two equal parallel divs with text in it:
<div class="xy">Example</div>
<div class="xy">This.is.a.example.of.a.long.Text</div>
The divs are wider than "Example" but less wide than "This.is.a.example.of.a.long.Text".
Now I want to see these texts left-aligned, so that the odd space is behind "Example" on the ride side.
But i also want to use
text-overflow:ellipsis
with the longer text in a way that I will see the end of the text.
So it should look like:
"Example "
"...ample.of.a.long.Text"
How do I do this?
I was just curious to your question and give it a try in java script as follows,
Demo
var maxChar = 20,
dots = '. . . ',
toTrim = $('.toTrim'),
toTrimLength = toTrim.text().length,
getChar = toTrimLength - maxChar;
if(toTrimLength > maxChar){
var newString = dots + toTrim.text().slice(getChar)
toTrim.text(newString);
}
I guess you still need to modify this to suite your requirement.
Also add the same string in title attr to see full text when hovering and if you want to do it even better. change text-overflow on :hover
this article will help you http://html5hub.com/ellipse-my-text/#i.6k1nyg11zqdiar
Anybody know how I could wrap the text in reverse order, from bottom to top?
I attached an example image.
[][http://i.stack.imgur.com/RVsIG.jpg]
Instead of breaking the line after it is full and having an incomplete line at the end, I need to brake somehow from bottom to top, so bottom lines are full and top line is incomplete.
I would not recommend using exotic CSS attributes which aren't even in Chrome & Firefox yet. The best cross-browser solution is to handle this in Javascript when the document loads. Here's a sketch of how to do that:
$(function() {
$(".title").each(function(i,title) {
var width = 0;
var originalHeight = $(title).height();
var spacer = $('<div style="float:right;height:1px;"/>').prependTo(title);
while (originalHeight == $(title).height()) {
spacer.width( ++width );
}
spacer.width( --width );
});
});
Working JSFiddle is here: http://jsfiddle.net/zephod/hfuu3m49/1/
6 years later, but fret not! I have found a pure CSS solution!
Turns out you can achieve this result with flexbox, but it's not obvious or very straight forward. This is what I started out with:
I want the header to be "bottom-heavy", the same effect as you describe in the question.
I began by splitting up my string by whitespace and giving them each a <span> parent. By using flex-wrap: wrap-reverse, and align-content: flex-start. You will achieve this:
Oh no! Now the order is messed up! Here comes the trick. By reversing both the order in which you add spans to the HTML and the direction order of flex with 'flex-direction: row-reverse', you actually achieve the "pyramid-shaped" upwards overflow effect you desire.
Here is my (simplified) code, using react and react-bootstrap:
<Row className='d-flex flex-wrap-reverse flex-row-reverse align-content-start'>
{props.deck.name
.split(' ')
.reverse()
.map(word => (
<span className='mr-1'>{word}</span>
))}
</Row>
There is no general css solution for it. You must have to utilize help of any language.
This is one of the solution using PHP:
<?php
$str= "This is what I want to achieve with your help";
$str = strrev($str);
$exp = str_split($str,18);
$str = implode(">rb<", $exp);
echo strrev($str);
?>
Well, if that is depending on the text, then you can try something like a word replacer. For example
var words = "This is what I want to achieve";
var newWords.replace("what", "what <br />"); // note the line break
document.write(newWords);
Here is a fiddle for you: http://jsfiddle.net/afzaal_ahmad_zeeshan/Ume85/
Otherwise, I don't think you can break a line depending on number of characters in a line.
Wrap and Nowrap will be rendered by the client-browser, so you can not force the browser to wrap from bottom to top. but you can do that with javascript or asp.
This is not a formal solution for this problem. But see if this helps.
The HTML CODE
<div id="mydiv">
I can imagine the logic behind the code having to detect what is the last line, detect the div size, and the font size... then measure how many characters it can fit and finally go to the above line and insert the break where necessary. Some font families might make this harder, but trial and error should solve the issue once the basic code is set..
</div>
CSS:
#mydiv
{
width:1000px;
line-height:18px;
font-size:20px;
text-align:justify;
word-break:break-all;
}
Here setting the div width around 50 times that of the font-size will give you the precise result. Other width values or font values might slightly disorient the last line, giving some blank space after the last character.(Could not solve that part, yet).
JQuery:
$(document).ready(function(){
//GET the total height of the element
var height = $('#mydiv').outerHeight();
//Get the height of each line, which is set in CSS
var lineheight = $('#mydiv').css('line-height');
//Divide The total height by line height to get the no of lines.
var globalHeight = parseInt(height)/parseInt(lineheight);
var myContent = $('#mydiv').html();
var quotient = 0;
//As long as no of lines does not increase, keep looping.
while(quotient<=globalHeight)
{
//Add tiny single blank space to the div's beginning
$('#mydiv').html(' '+myContent);
//Get the new height of line and height of div and get the new no of lines and loop again.
height = $('#mydiv').outerHeight();
lineheight = $('#mydiv').css('line-height');
quotient = parseInt(height)/parseInt(lineheight);
myContent = $('#mydiv').html();
}
//get the final div content after exiting the loop.
var myString = $('#mydiv').html();
//This is to remove the extra space, which will put the last chars to a new line.
var newString = myString.substr(1);
$('#mydiv').html(newString);
});
If you already know where you want your breaks to take place just use simple HTML breaks to break your content and have it display the way you want.
<p>This is what<br/>
want to acheive with your help</p>
If you set the breaks manually (and you know where you want them to break) then create them yourself.
You could also try setting separate css width adjustments based on the dimensions of the screen you are seeing the breaking you are not liking and set an #media reference to make the div width smaller to break the text so it doesn't run unevenly across the top of certain size devices.
Use display: inline-block; on the text div.
I have a MultiLine asp:Textbox (a standard html textarea for those non-asp people) that I want to be auto-sized to fit all it's content only through css. The reason for this is that I want it to be of a specified height in the web browser, with scrolling enabled.
I have implemented a print style sheet however and want all text located in the textarea to be displayed when printed with no overflow hidden.
I can manually specify the height of the textarea in the print.css file problem with this being that the fields are optional and a 350px blank box is not optimal and there is always the possibility of a larger amount of text than this...
I have tried using :
height: auto;
height: 100%;
In IE and Firefox respectively yet these seem to be overridden by the presence of a specified number of rows in the html mark-up for the form which must be generated by .NET when you do not specify a height on the asp:Textbox tag, this seems to only accept numercial measurements such as px em etc...
Any ideas?
What you are asking for (a css solution) is not possible.
The content of the textarea is not html elements, so it can not be used by css to calculate the size of the textarea.
The only thing that could work would be Javascript, e.g. reading the scrollHeight property and use that to set the height of the element. Still the scrollHeight property is non-standard, so it might not work in all browsers.
jQuery or a javascript function to find and make textboxes bigger might be the best solution - at least thats what i found
we use this set of functions and call clean form after the page is loaded (i know this isnt the best solution right here and am working to transfer to a jQuery solution that is more elegant) - one note make sure your textareas have rows and cols specified or it doesnt work right.
function countLines(strtocount, cols)
{
var hard_lines = 1;
var last = 0;
while (true)
{
last = strtocount.indexOf("\n", last + 1);
hard_lines++;
if (last == -1) break;
}
var soft_lines = Math.round(strtocount.length / (cols - 1));
var hard = eval("hard_lines " + unescape("%3e") + "soft_lines;");
if (hard) soft_lines = hard_lines; return soft_lines;
}
function cleanForm()
{
var the_form = document.forms[0];
for (var i = 0, il = the_form.length; i < il; i++)
{
if (!the_form[i]) continue;
if (typeof the_form[i].rows != "number") continue;
the_form[i].rows = countLines(the_form[i].value, the_form[i].cols) + 1;
}
setTimeout("cleanForm();", 3000);
}
If you set rows to be a ridiculously high number the CSS height rule should override it on the screen.
Then in the print stylesheet just set height to auto. This might result in some big blank space where all the available rows haven't been filled up, but at least all text will be visible.
give jQuery's autogrow() a go #
http://plugins.jquery.com/project/autogrow