How to choose the first word in the last line of text? (<h1> - <h6>, <p>)
I need to add a small horizontal line under each h1-h6 element on the site that I am creating. I am currently adding an after element:
h1, h2, h3, h4, h5, h6 { text-transform: uppercase; color: #000; margin-top: 0; margin-bottom: 2rem; font-weight: 800; color: #333; display: inline-block; position: relative; text-rendering: optimizeLegibility; font-family: $altfont; position: relative; &:after { content: ''; position: absolute; bottom:0; left:0; width: 60px; height: 4px; background-color: $yellow; } } I also have a little jQuery function to make sure that the after element is always 20px below the element:
$(function () { $('h1, h2, h3, h4, h5, h6').each(function () { x = parseInt($(this).css('font-size')); $(this).css('line-height', (x + 20) + 'px'); }); }); this works if h1 has text alignment on the left - when the text is wrapped in two or more lines, the after element will be displayed under the first word of the last line.
The problem is that if the text is centered or right-aligned, the after element will be displayed under the h1 element, but not under the first word of the last line. Is this something that can be done using JS / jQuery?
Here is an example of what is happening. In the second example, I would like the yellow line to appear under the word "Slice".
EDIT
It was a good challenge! To achieve what you ask, 4 loops are required.
- To add a
spanfor each word. - To find the
spanoffset in the last line. - To remove spans in all lines except the last.
- To remove spaces in all words except the first.
See comments in the code (I left all my debugs console.logs).
$(function () { $('h1, h2, h3, h4, h5, h6').each(function () { x = parseInt($(this).css('font-size')); $(this).css('line-height', (x + 20) + 'px'); findWord($(this)); }); function findWord(el){ // Get the word array. var wordArr = el.html().split(" "); // Cycle words to add span on each words. for(i=0;i<wordArr.length;i++){ console.log(wordArr[i]); wordArr[i] = "<span class='underliner'>"+wordArr[i]+"</span>"; } // Update HTML. el.html(wordArr.join(" ")); // Find the offset of the last line. var biggestOffset=0; el.find(".underliner").each(function(){ console.log($(this).offset().top); if($(this).offset().top>biggestOffset){ biggestOffset=$(this).offset().top; } }); console.log("biggestOffset: "+biggestOffset); // Remove span on NOT the last line el.find(".underliner").each(function(){ if($(this).offset().top<biggestOffset){ $(this).replaceWith($(this).html()); } }); // On the last line, remove all spans except on the first word el.find(".underliner").not(":eq(0)").each(function(){ $(this).replaceWith($(this).html()); }); } }); h1, h2, h3, h4, h5, h6 { text-transform: uppercase; color: #000; margin-top: 0; margin-bottom: 2rem; font-weight: 800; /*color: #333;*/ display: inline-block; position: relative; text-rendering: optimizeLegibility; font-family: $altfont; position: relative; text-align:center; /* ADDED */ /* REMOVED */ /*&:after { content: ''; position: absolute; bottom:0; left:0; width: 60px; height: 4px; background-color: &yellow; }*/ } .underliner{ text-decoration:underline; text-decoration-color: red } <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <h1>This is a quite long H1 that will certainly wrap</h1> <h2>This is a quite long H2 that will certainly wrap</h2> <h3>This is a quite long H3 that will certainly wrap</h3> <h4>This is a quite long H4 that will certainly wrap</h4> <h5>This is a quite long H5 that will certainly wrap</h5> <h6>This is a quite long H6 that will certainly wrap</h6> First answer
(incorrectly put the question ...)
Since you are already using jQuery ...
You can add $(this).append($("<div class='smallBar'>").css({"top":x+10})) in the script ...
And use CSS to define a smallBar in the same way as for the after pseudo-element.
$(function () { $('h1, h2, h3, h4, h5, h6').each(function () { x = parseInt($(this).css('font-size')); $(this).css('line-height', (x + 20) + 'px'); $(this).append($("<div class='smallBar'>").css({"top":x+10})) }); }); h1, h2, h3, h4, h5, h6 { text-transform: uppercase; color: #000; margin-top: 0; margin-bottom: 2rem; font-weight: 800; /*color: #333;*/ display: inline-block; position: relative; text-rendering: optimizeLegibility; font-family: $altfont; position: relative; /*&:after { content: ''; position: absolute; bottom:0; left:0; width: 60px; height: 4px; background-color: &yellow; }*/ } .smallBar{ position: absolute; top:0; left:0; width: 60px; height: 4px; background-color: red; } <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <h1>This is a quite long H1 that will certainly wrap</h1> <h2>This is a quite long H2 that will certainly wrap</h2> <h3>This is a quite long H3 that will certainly wrap</h3> <h4>This is a quite long H4 that will certainly wrap</h4> <h5>This is a quite long H5 that will certainly wrap</h5> <h6>This is a quite long H6 that will certainly wrap</h6> My algorithm for finding and highlighting the first word of the last line:
- Iterate through the elements.
- Get the text of an element (
innerText). - Separate text by space ("").
- Remove all elements from the block.
- Add a
spanwith words and spaces for text nodes (also save thisspanin an array). - Sort this array with a
spanwith the highesttopand lowestleftspanrectangle. - Add a highlight class to the top of this array.
highlightFirstWordOfLastLineForHeaders(); /* apply highlighting on window resize */ window.addEventListener("resize", highlightFirstWordOfLastLineForHeaders); /* Peforms highlightFirstWordOfLastLine call for every header element */ function highlightFirstWordOfLastLineForHeaders() { var elements = document.querySelectorAll("h1, h2, h3, h4, h5, h6"); for (var i = 0; i < elements.length; i++) { highlightFirstWordOfLastLine(elements[i]); } } /* Peforms highlighting for single element */ function highlightFirstWordOfLastLine(element) { var text = element.innerText.trim(); /* get words list */ var words = text.split(" "); /* removing all elements */ while (element.firstChild) { element.removeChild(element.firstChild); } var spanArray = []; /* add spans with words and whitespaces */ for (var i = 0; i < words.length; i++) { /* append span with word */ var span = document.createElement("span"); span.appendChild(document.createTextNode(words[i])); element.appendChild(span); /* append whitespace */ element.appendChild(document.createTextNode(" ")); /* save span element to array */ spanArray.push(span); } /* sorting by highest top and lowest left */ spanArray.sort(function(a, b) { var rectA = a.getBoundingClientRect(); var rectB = b.getBoundingClientRect(); var deltaTop = rectB.top - rectA.top; /* if differense is less then 1px */ if (Math.abs(deltaTop) < 1) { return rectA.left - rectB.left; } return deltaTop; }); /* appending highlighting to fist word of last line */ spanArray[0].classList.add("selected"); } .selected { border-bottom: 2px solid yellow; } <h1> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent ultrices porta nibh, eu semper massa ullamcorper et. </h1> <h2> Curabitur in venenatis sapien. Nullam cursus ante ac enim dapibus, a egestas tellus mollis. </h2> <h3> Aenean tincidunt ligula et egestas suscipit. Proin euismod felis leo, egestas porta purus porta vitae. Morbi ut pulvinar quam, eu accumsan eros. Duis tristique pretium imperdiet. Integer nunc odio, consectetur vel leo vel, posuere venenatis nunc. Vestibulum sit amet arcu sit amet tortor faucibus maximus in id massa. Praesent vulputate, tellus nec aliquam tempor, ipsum erat tincidunt est, sit amet cursus turpis dui ac ipsum. Duis nec odio in felis aliquet sagittis sit amet in leo. Suspendisse potenti. Curabitur vitae sagittis diam. Duis id lectus cursus purus ultricies sollicitudin. Phasellus a ex sit amet eros lacinia fringilla sit amet sit amet sem. Proin luctus ornare risus at volutpat. Aliquam eleifend porttitor nulla. Sed facilisis mattis felis ut sodales. </h3> <h4> Quisque quis ultricies arcu. Aliquam feugiat non ipsum quis malesuada. Suspendisse ullamcorper, eros ut maximus hendrerit, sem velit lobortis turpis, non tristique justo magna eu nulla. Vestibulum maximus auctor ipsum sit amet finibus. Etiam commodo iaculis sem, vitae tincidunt libero. Phasellus lacus quam, semper ut pulvinar sed, accumsan a arcu. Suspendisse nulla velit, rhoncus id porttitor at, dictum ac ante. Sed eleifend vitae quam eget pretium. </h4> <h5> Duis semper rhoncus ultrices. Aliquam id elit nec quam fringilla gravida ac sed nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec lorem leo, rhoncus quis nulla eget, posuere pellentesque leo. Nam in nisi sit amet sem consequat sodales. Quisque leo urna, aliquet eu malesuada at, laoreet et lorem. Fusce est neque, fringilla id ex id, semper tincidunt mauris. Aliquam erat volutpat. Vestibulum sodales sodales tincidunt. Pellentesque elementum quis neque vel malesuada. Nulla non scelerisque enim, ut posuere tortor. Fusce vel neque tristique ipsum aliquet mattis at sed sapien. </h5> <h6> Praesent scelerisque magna libero, vitae commodo purus tincidunt in. Etiam placerat diam turpis, sit amet iaculis eros efficitur at. Sed porta, dui non condimentum vestibulum, libero enim cursus nibh, eu luctus ligula ante sed neque. Duis vestibulum lacus felis, at iaculis tellus malesuada at. Donec vehicula lacinia metus. Pellentesque non efficitur lectus. Etiam et consectetur massa. Proin et leo cursus, convallis diam sit amet, mattis arcu. Fusce posuere pharetra leo eget volutpat. Maecenas et posuere urna. Nullam a dolor eu ipsum placerat rhoncus. In cursus, mauris ut pulvinar pellentesque, diam orci facilisis ante, at sollicitudin libero turpis in libero. In maximus est eu nisl venenatis ultrices. </h6> You can also see how it works with window resizing using jsFiddle .
Although this has not been verified, this logic makes sense.
Write a JS looking for size h1 if size h1 is greater than X, which will be size h1, perhaps a little added on top for a good estimate.
Then add style to h1 with and: after the pseudo class that appears at the bottom: 0 on the left: 0. this, however, means that your text should be left aligned and not center.
When h1 is wrapped in 2 lines, the size of h1 is essentially doubled, which will trigger the if statement and add style to the text. This is the closest answer I have to fix for you. I apologize if you need a text that will be central.
EDIT: sample code above .
Now I canβt say that this will work, but here is some basic code based on the previously explained method. Im not surprising with javascript, so you will most likely have to rely on. But at least it gives you a starting point, I hope ...
Js
$(document).ready(function() { var h1Height = document.getElementsByTagName("h1").offsetHeight; function firstH1Wrap() { if (h1Height < /*line-height+5px*/){ document.getElementsByTagName("h1").addClass('hide-line'); } else { document.getElementsByTagName("h1").removeClass('hide-line'); } } }); $(window).resize(firstH1Wrap).trigger('resize') CSS
h1, h2, h3, h4, h5, h6 { text-transform: uppercase; color: #000; margin-top: 0; margin-bottom: 2rem; font-weight: 800; color: #333; display: inline-block; position: relative; text-align: left; text-rendering: optimizeLegibility; font-family: $altfont; position: relative; } h1:after, h2:after, h3:after, h4:after, h5:after, h6:after { content: ''; position: absolute; bottom:0; left:0; width: 60px; height: 4px; background-color: $yellow; display: block; } h1.hide-line:after, h2.hide-line:after, h3.hide-line:after, h4.hide-line:after, h5.hide-line:after, h6.hide-line:after { display: none; } If you had to set the row height for all the headers 1-6, then you could perform the same function for all of them, since they are the same size, if you do not, you will need to declare a new variable for each of headers and embed it in a function or create new ones.
This cannot be fixed based on how you wrote the code. But I think I have a solution if you want to change a few things. My method will only work if you know the font-size property of the element you are addressing.
UPDATED CSS
h1 { font-size: 24px; /* ADDED */ text-transform: uppercase; color: #000; margin-top: 0; margin-bottom: 2rem; font-weight: 800; color: #333; display: inline-block; position: relative; text-rendering: optimizeLegibility; font-family: $altfont; position: relative; &:after { content: ''; position: absolute; top: 24px; /* FONT SIZE = DISTANCE */ left:0; width: 60px; height: 4px; background-color: $yellow; } } This should work, but it has not been verified.
It works by knowing font-size and then using this value as the top field. Thus, it will always be in the same place, regardless of changing the alignment of the text.
EDIT: Now I understand what you would like to do.
This is very difficult to do, because the line can be in many different places. The only solution I can come up with from my head is:
Media Inquiries
You will need to create a breakpoint at any time and whenever the text wraps around or the first word of the last line occurs.
