2

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

enter image description here

  • In #1, there's a "long" text and long lines. That is how I want it to behave.
  • In #2, there's a short text and one long line. I don't like it.

I would like:

  • 2 to be like #3 without having to add that <br> manually.

  • to use the same HTML & CSS for both "long" and short texts.

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)

Community
  • 1
  • 1
olivier
  • 1,007
  • 8
  • 14
  • Are two lines the maximum? – Itay Sep 09 '13 at 17:56
  • Unfortunately, no. I would prefer a solution without maximum, but I think it is safe to take 4 lines as a maximum for my use case. – olivier Sep 09 '13 at 17:59
  • I don't think it's possible with only CSS... But lets see. – Itay Sep 09 '13 at 18:00
  • Well, if there is no other way, I will look for a progressive enhancement solution with some JavaScript, but I would obviously prefer a full CSS solution. About the maximum, I could see a solution taking maximum 2 short lines while allowing more long lines if that helps. – olivier Sep 09 '13 at 18:04
  • Do you always want at least 2 lines? How many words minimum? – frenchie Sep 09 '13 at 18:10
  • Unfortunately, no. If the text is very short (e.g. "Cras pretium"), I only want one line. One word minimum. It is user generated content. – olivier Sep 09 '13 at 18:11
  • @olivier remember to accept/up vote if my answer works – Tom Prats Sep 09 '13 at 20:22

5 Answers5

1

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

Community
  • 1
  • 1
Tom Prats
  • 7,364
  • 9
  • 47
  • 77
  • Seems to work, here's a quick test: http://codepen.io/anon/pen/FmCiE (I made some correction to your code and added line-height property in CSS). – olivier Sep 09 '13 at 18:53
  • awesome, I updated it to include your changes (let me know if I missed one!) – Tom Prats Sep 09 '13 at 19:00
  • It is `h1` instead of `h3`, but that's not a big deal. However, while trying with more examples, I faced a new problem (not especially linked to your solution): I would like the shortest line to be the first, not the last. Have you got any ideas to solve this one? – olivier Sep 09 '13 at 19:08
  • I meant I don't want to add a `
    ` manually. Since I will be using some JavaScript, it is okay (and better than to `

    `, in my opinion). Thank you for your answer. It seems like it will only work for one or two lines, but I will play with it tomorrow (UTC+2 here), try to extend it if necessary and let you know how it worked out.

    – olivier Sep 09 '13 at 20:38
  • yeah i was still coding for the possibility of 2 lines, ill fix it so it can handle multiple – Tom Prats Sep 09 '13 at 20:40
  • See my answer to see what I came up with, inspired by your script. – olivier Sep 10 '13 at 12:46
1

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.

Mathijs Flietstra
  • 12,900
  • 3
  • 38
  • 67
  • Unfortunately, I couldn't get it to work as I want for this use case (I hate #8 in your jsFiddle). This said, MediaClass looks awesome and I will definitely keep it in mind, thank you for the discovery. – olivier Sep 10 '13 at 12:39
0

You need JavaScript to do this. CSS cannot format text based on a complex set of rules.

Diodeus - James MacFarlane
  • 112,730
  • 33
  • 157
  • 176
0

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

Nick Giampietro
  • 178
  • 1
  • 8
  • This doesn't seem to work, I must be missing something: http://codepen.io/anon/pen/pgKFi (`splitString.length` seems to always return `1`) – olivier Sep 09 '13 at 19:21
  • I was missing something: `newString.split(" ")` should be `aString.split(" ")`. Still, I couldn't get it to work like I wanted, even by playing with the parameters. – olivier Sep 10 '13 at 12:27
  • It wasn't ready to run right from the beginning, because it wasn't written to run automatically. I re-wrote it, using ideas from some other working answers, for a better working solution.Please let me know what you think. – Nick Giampietro Sep 10 '13 at 17:09
  • I know it wasn't designed to run automatically, that is why I added a bit of jQuery at the end to execute it on each boxes. Your new example isn't working well in the pen (#2 should look like #3), but I must say using `text-indent` is a pretty interesting idea. – olivier Sep 10 '13 at 20:47
0

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.

Community
  • 1
  • 1
olivier
  • 1,007
  • 8
  • 14