Shorten string without cutting words in JavaScript

150,651

Solution 1

If I understand correctly, you want to shorten a string to a certain length (e.g. shorten "The quick brown fox jumps over the lazy dog" to, say, 6 characters without cutting off any word).

If this is the case, you can try something like the following:

var yourString = "The quick brown fox jumps over the lazy dog"; //replace with your string.
var maxLength = 6 // maximum number of characters to extract

//trim the string to the maximum length
var trimmedString = yourString.substr(0, maxLength);

//re-trim if we are in the middle of a word
trimmedString = trimmedString.substr(0, Math.min(trimmedString.length, trimmedString.lastIndexOf(" ")))

Solution 2

There are lots of ways to do it, but a regular expression is a useful one line method:

"this is a longish string of text".replace(/^(.{11}[^\s]*).*/, "$1"); 
//"this is a longish"

This expressions returns the first 11 (any) characters plus any subsequent non-space characters.

Example script:

<pre>
<script>
var t = "this is a longish string of text";

document.write("1:   " + t.replace(/^(.{1}[^\s]*).*/, "$1") + "\n");
document.write("2:   " + t.replace(/^(.{2}[^\s]*).*/, "$1") + "\n");
document.write("5:   " + t.replace(/^(.{5}[^\s]*).*/, "$1") + "\n");
document.write("11:  " + t.replace(/^(.{11}[^\s]*).*/, "$1") + "\n");
document.write("20:  " + t.replace(/^(.{20}[^\s]*).*/, "$1") + "\n");
document.write("100: " + t.replace(/^(.{100}[^\s]*).*/, "$1") + "\n");
</script>

Output:

1:   this
2:   this
5:   this is
11:  this is a longish
20:  this is a longish string
100: this is a longish string of text

Solution 3

I am kind of surprised that for a simple problem like this there are so many answers that are difficult to read and some, including the chosen one, do not work .

I usually want the result string to be at most maxLen characters. I also use this same function to shorten the slugs in URLs.

str.lastIndexOf(searchValue[, fromIndex]) takes a second parameter that is the index at which to start searching backwards in the string making things efficient and simple.

// Shorten a string to less than maxLen characters without truncating words.
function shorten(str, maxLen, separator = ' ') {
  if (str.length <= maxLen) return str;
  return str.substr(0, str.lastIndexOf(separator, maxLen));
}

This is a sample output:

for (var i = 0; i < 50; i += 3) 
  console.log(i, shorten("The quick brown fox jumps over the lazy dog", i));

 0 ""
 3 "The"
 6 "The"
 9 "The quick"
12 "The quick"
15 "The quick brown"
18 "The quick brown"
21 "The quick brown fox"
24 "The quick brown fox"
27 "The quick brown fox jumps"
30 "The quick brown fox jumps over"
33 "The quick brown fox jumps over"
36 "The quick brown fox jumps over the"
39 "The quick brown fox jumps over the lazy"
42 "The quick brown fox jumps over the lazy"
45 "The quick brown fox jumps over the lazy dog"
48 "The quick brown fox jumps over the lazy dog"

And for the slug:

for (var i = 0; i < 50; i += 10) 
  console.log(i, shorten("the-quick-brown-fox-jumps-over-the-lazy-dog", i, '-'));

 0 ""
10 "the-quick"
20 "the-quick-brown-fox"
30 "the-quick-brown-fox-jumps-over"
40 "the-quick-brown-fox-jumps-over-the-lazy"

Solution 4

Everyone seems to forget that indexOf takes two arguments- the string to match, and the character index to start looking from. You can break the string at the first space after 10 characters.

function cutString(s, n){
    var cut= s.indexOf(' ', n);
    if(cut== -1) return s;
    return s.substring(0, cut)
}
var s= "this is a long string i cant display";
cutString(s, 10)

/*  returned value: (String)
this is a long
*/

Solution 5

Lodash has a function specifically written for this: _.truncate

const truncate = _.truncate
const str = 'The quick brown fox jumps over the lazy dog'

truncate(str, {
  length: 30, // maximum 30 characters
  separator: /,?\.* +/ // separate by spaces, including preceding commas and periods
})

// 'The quick brown fox jumps...'
Share:
150,651
Josh Bedo
Author by

Josh Bedo

I'm a 22 year old developer who loves learning and building new things

Updated on July 08, 2022

Comments

  • Josh Bedo
    Josh Bedo almost 2 years

    I'm not very good with string manipulation in JavaScript, and I was wondering how you would go about shortening a string without cutting any word off. I know how to use substring, but not indexOf or anything really well.

    Say I had the following string:

    text = "this is a long string I cant display"
    

    I want to trim it down to 10 characters, but if it doesn't end with a space, finish the word. I don't want the string variable to look like this:

    "this is a long string I cant dis"

    I want it to finish the word until a space occurs.

    • Chance
      Chance about 13 years
      you mean trim a string? try " too many spaces ".trim()
    • Gromski
      Gromski about 13 years
      Some example input and expected output would help a lot in answering this question.
    • Josh Bedo
      Josh Bedo about 13 years
      alright sorry say i had the string text = "this is a long string i cant display" i want to trim it down to 10 characters but if it doesnt end with a space finish the word i don't want the string variable to look like this "this is a long string i cant dis"
  • Josh Bedo
    Josh Bedo about 13 years
    Awesome, i literally googled this question a million ways and could only find a working version for php nothing close to this and involving loops.
  • Hamish
    Hamish about 13 years
    nice one - also easier to make the length variable that my regex solution.
  • Hamish
    Hamish about 13 years
    It's referring to the first (and only, in this case) sub expression match - the stuff in the brackets. $0 would refer to the whole match, which in this case is the whole string.
  • Josh Bedo
    Josh Bedo about 13 years
    seems to be printing out everything still.
  • Josh Bedo
    Josh Bedo about 13 years
    alright thanks a lot, i'm guessing a max value variable isn't possible?
  • Josh Bedo
    Josh Bedo about 13 years
    will have to try the edited version i just found out .replace doesn't work in jquery functions ex:.each so i have to figure out a way around that.
  • Pointy
    Pointy about 13 years
    @josh it's absolutely not true that ".replace" does not work in "jQuery functions". There's not even any such thing as a "jQuery function".
  • rjmackay
    rjmackay over 10 years
    @josh You should be able to make max length a variable by using a regexp object: t.replace(new RegExp("^(.{"+length+"}[^\s]*).*"), "$1")
  • rjmackay
    rjmackay over 10 years
    Also worth noting that this breaks with a multi-line input.. since the regex matches the first line longer than max-length, rather than matching from the start.
  • Beytan Kurt
    Beytan Kurt over 10 years
    isn't that should be "maxLength + 1". And if maxLength is greater or equal than the complete sentence length, than the last word is not included. but thanks for the solution.
  • Sebastien Lorber
    Sebastien Lorber almost 10 years
    based on your answer, I've made a proper function to handle this: stackoverflow.com/a/24168721/82609
  • tylerl
    tylerl over 9 years
    If using this on a string that's shorter than the maxLength, the last word gets cut off. Maybe @AndrewJuniorHoward already stated the fix for this (maxLength + 1), but I fixed it by simply adding this line up top: var yourString += " ";
  • Scheintod
    Scheintod over 9 years
    Note that indexOf can be replaced by lastIndexOf if hard boundaries are needed.
  • Shashank Agrawal
    Shashank Agrawal over 8 years
    @Hamish your option works well, but it is including the last word also if the length exceeds. I tried altering the regex expression to exclude the last word if the max word limit is exceeding but didn't get it working. How can we achieve that?
  • JKesMc9tqIQe9M
    JKesMc9tqIQe9M over 8 years
    I like this solution, but shouldn't the args passed to $.extend be reversed?
  • loretoparisi
    loretoparisi about 8 years
    @Hamish look at this: supposed to have a var maxLength=140,text.length>=maxLength I have: text.replace(/^(.{140}[^\s]*).*/, "$1").length ok but text.replace( new RegExp("/^(.{"+maxLength+"}[^\s]*).*/"), "$1").length not, why?
  • Ethan Chen
    Ethan Chen almost 8 years
    This doesn't seem to work if there are non-English characters towards the end of the string.
  • Andrey Gordeev
    Andrey Gordeev almost 8 years
    Unfortunately, if you take away fox jumps over the lazy dog part, the result will be The quick brown , when it should be The quick brown fox.
  • Chris Cinelli
    Chris Cinelli over 7 years
    This always cuts the last word.
  • Tici
    Tici over 7 years
    I completely forgot about the lastIndexOf(). Good catch!
  • naz786
    naz786 about 7 years
    This is a good solution, however if the purpose was to make text fit in a wide section, and there was 2 or 3 words in the string, if there was plenty of space and no overflow out of the section, this code would delete a whole word unnecessarily. So for the small string "Hello my name is Yusef", if it was in a wide div section: ["Hello my name is Yusef" ______] it will unsuccessfully change it to ["Hello my name is"______]. In my case, I wanted to keep whole words to fill a section.
  • Al-Mothafar
    Al-Mothafar almost 7 years
    Well, this is not really working correctly, sometimes I pass the maximum value as for example if the last word was 30 character already it will be with length more than 60 already! even if it set length to {30}
  • Hamish
    Hamish almost 7 years
    @Al-Mothafar right well the question does ask "without cutting words" and I do say "plus any subsequent non-space characters" so it's working exactly as described. If you've got different needs you'll need to modify accordingly.
  • Al-Mothafar
    Al-Mothafar almost 7 years
    Well, in my opinion, the question does not specify, but make more sense with limitation to not exceed it anyway, so if he wants "this is a long string i cant display" or "this is a long string i can't", in your answer the result will show the last word "display" but in my case if limitation should be the second case.
  • Saumya Rastogi
    Saumya Rastogi almost 7 years
    @NT3RP - According to your provided solution, the strings with the lesser length will not be truncated as desired.. Just for example please try the string - "Clever fox jumps over lazy dog", after giving any number to maxLength, the output according to your solution will be: Clever fox jumps over lazy only, the last word dog will be missed every time...
  • Jeremy Daalder
    Jeremy Daalder almost 7 years
    So just wrap it in a test for the length of the string if (yourString.length > maxLength) ....
  • Silvain
    Silvain over 6 years
    This crashes if for some reason str is undefined. I added if (!str || str.length <= maxLen) return str;
  • hlozancic
    hlozancic over 6 years
    One of the better and smarter solutions out there for a simple problem like this :) Good job
  • hlozancic
    hlozancic over 6 years
    Btw if you want to cut string like this which is multiline use this regex: ^([\s\S]{50}[^\s]*)[\s\S]*
  • shrewquest
    shrewquest about 6 years
    this doesn't handle the edge case where the separator doesn't occur in the string
  • Chris Cinelli
    Chris Cinelli about 6 years
    @shrewquest It works. If the separator is not in the string, it returns the string itself if str.length <= maxLen. Otherwise it returns an empty string.
  • ChristoKiwi
    ChristoKiwi almost 6 years
    You wrote a JS function that truncates a string to a number of words. Read the question again.
  • Mike Aron
    Mike Aron almost 6 years
    eeehm. i think this is the only right answer to the question. He asked without cutting the word.
  • Shishir Arora
    Shishir Arora almost 5 years
    @JeremyDaalder even then what if trimmedString ends with a space like "Clever fox jumps ".. better to trim() before returning
  • PSaul
    PSaul over 3 years
    This doesn't account for the 'join(' ') characters, which can make the string longer than limit. If you change reduce()'s function to (acc, cur, idx) and the if to (acc + cur.length <= limit - idx) then it will account for the extra spaces when the words are joined back together. If being strictly within limit is required.
  • Bananaapple
    Bananaapple over 3 years
    The trailing .* in the regex isn't necessary and could negatively impact performance.
  • ggorlen
    ggorlen almost 3 years
    This potentially cuts off part of a word.
  • Niclas
    Niclas over 2 years
    This returns empty string if str>maxLen. Solved it by adding if(str.split(separator)[0].length>maxLen) return str.split(separator)[0].substr(0,maxLen-2)+"...";
  • Conor Reid
    Conor Reid about 2 years
    Thank you @hlozanic, This is what I was looking for and this should be appended to this top answer.
  • Wajid
    Wajid about 2 years
    without the modification of @BeytanKurt this solution is not applicable.