Reputation: 1017
I would like to control the way a text is displayed in box, preferring new lines to long lines, but still allowing long lines.
Here's some examples: http://codepen.io/anon/pen/jiCxo
I would like:
<br>
manually.Also, I would like the first line to be the shortest, not the last one: http://codepen.io/anon/pen/irFcK.
Any ideas?
(if, as I fear, it is not possible using only CSS, I am open to a nice JavaScript solution)
Upvotes: 2
Views: 353
Reputation: 1017
Inspired by TMP's answer and this answer to another question, I came up with this script that works great in most situations but still has some quirks…
var minimum_gap = 5; // Minimum gap on the last line to try to put it on the first.
$(".box h1").each(function() {
var h1 = $(this),
text = h1.text(),
words = text.split(' '),
lines = [],
first_word = [],
l = 0,
i = 0;
// Adding a span to measure the width
h1.wrapInner('<span>');
var span = h1.find('span');
// Put the first word in the span and save the height
span.text(words[0]);
var height = h1.height();
// Measure width of each line
for(var i = 1; i < words.length; i++){
span.append(' ' + words[i]);
// If there's a new line
if(h1.height() > height){
lines[l] = span.width();
span.text(words[i]);
l++;
}
}
// Last line
lines[l] = span.width();
var nb_lines = lines.length;
ideal_line_width = 0;
if(nb_lines == 1) {
// If the line is "long", make it two
if(span.width() >= h1.parent().width()/2) {
ideal_line_width = h1.width()/2;
nb_lines = 2;
}
}
else {
// Compute the average lines width (except for the last one)
var sum = 0;
for(l=0;l<(nb_lines-1);l++) {
sum += lines[l];
}
var avg_width = sum/(nb_lines-1);
// If last line is shorter than the average
if(lines[nb_lines-1] < avg_width) {
var gap = avg_width - lines[nb_lines-1];
// Spread the gap among the lines
gap /= nb_lines;
if(gap > minimum_gap) {
ideal_line_width = avg_width - gap;
}
}
}
// Let's make the wanted adjustments
if(ideal_line_width != 0) {
// Determining which is the first word of each line, beginning at the end in order to get the shortest one first
l = nb_lines-1;
span.empty();
for(i=words.length-1;i>=0;i--) {
span.prepend(words[i] + ' ');
if(span.width() > ideal_line_width) {
// If there's a new line, we cancel the last word
if(h1.height() > height) {
i++;
}
span.empty();
first_word[l] = i;
l--;
}
}
// Applying the results
span.remove();
l = 1;
for(i=0;i<words.length;i++) {
h1.append(' ' + words[i]);
if(first_word[l] == i+1) {
h1.append('<br>');
l++;
}
}
}
// Or just display the text
else {
span.remove();
h1.text(text);
}
});
You can see it in action here. Unfortunately, I still don't like #8 and #13. Tips and improvements are welcome.
Upvotes: 0
Reputation: 228
I don't think you can do that with only CSS. Here's a Javscript snippet that you can try implementing to parse your strings. It's clunky and untested but allows you to have a preset both for the char length you want, as well as a word length. I'm sure you could clean it up and make it fit your needs.
// requires h1 field to have line-height property set
$(".box h1").each(function()
{
// splits if over this % width
var preset = 50;
// indents first line to same as preset percentage
// to attempt to make first line shorter
var indent = String(preset) + 'px';
// height of a standard line
var lh = parseInt($(this).css('line-height'));
// width of the box
var w = $(this).parent().width() * (preset/100);
// if the text field is one line heigh & over the preset
if(($(this).height() <= lh) && ($(this).width() >= w))
{
$(this).width(w);
$(this).css('text-indent' , indent);
}
// otherwise it's fine
});
Here's a link: http://codepen.io/jnickg/pen/szIpd
Upvotes: 0
Reputation: 12974
I have come up with a solution which utilizes a still fairly unknown JS library named MediaClass which enables the use of media queries with specific elements on the page.
I think it looks pretty good the way I've set the values but you might want to fine-tune it a little by changing widths in the JS or the CSS. Here's a jsFiddle for your tinkering pleasure.
The way it works:
JS:
MediaClass("large", "h1:media(this-min-width: 300px)");
MediaClass("small", "h1:media(this-min-width: 200px and this-max-width: 300px)");
These lines ensure that a small
class is added to h1
if h1
's width is between 200px
and 300px
and a large
class if h1
is wider than 300px
.
CSS:
.large:before {
content:"\A";
width: 50%;
display: inline-block;
}
.small:before {
content:"\A";
width: 30%;
display: inline-block;
}
This bit adds a :before
pseudo-element of a width depending on the width of the h1
inside the h1
, before the text, this moves the first line inside the h1
over, which changes the flow of the text.
Edit: I fixed up the post and the fiddle to better demonstrate how this solution answers the question asked.
Upvotes: 1
Reputation: 7921
I made a quick function that should do what you want. I commented it so you know what's going on.
$(".box h1").each(function() {
// Check if one line
if($(this).height() <= parseInt($(this).css('line-height'))){
// Check if width is greater than %50 of parent
if($(this).width() >= $(this).parent().width()/2){
// Adjust width to put it on two lines
$(this).width($(this).parent().width()/2)
}
}
});
EDIT:
To have the first line shorter than the second line, you have to do something a bit more complex. I used cut from this answer. This should be pretty close to what you want.
$(".box h1").each(function() {
// Check if one line
if($(this).height() <= parseInt($(this).css('line-height'))){
// Check if width is greater than %50 of parent
if($(this).width() >= $(this).parent().width()/2){
// First find approximately where you want it
place = Math.round($(this).val().length/2); // Might need a parseFloat
// Find nearest end of word in correct direction
original = $(this);
first = original.text(cut(place));
end = first.val().length
start = $(this).val().length - end
second = $(this).substr(start,end)
// Place a break tag in the middle to put it on two lines
$(this).html(first + <br> + second)
}
}
});
Here's cut
function cut(n) {
return function textCutter(i, text) {
var short = text.substr(0, n);
if (/^\S/.test(text.substr(n)))
return short.replace(/\s+\S*$/, "");
return short;
};
}
This code uses a <br>
to break up two lines (with the second longer)
EDIT2:
It's impossible to have the line lengths be different without a <br>
or some other way of adding a new line. If you like it better I can change it so it uses multiple <h1>
tags, which I think will automatically kick the each addition tag to a new line
Upvotes: 1
Reputation: 114417
You need JavaScript to do this. CSS cannot format text based on a complex set of rules.
Upvotes: 0