CSS text justify with letter spacing - css

Is there a way to automatically justify words using letter spacing, each in its row, to a defined width, using CSS?
For example, "Something like this" would look, well, something like this:
Is there a non-obtrusive way to apply such styling to my text? I believe pure CSS doesn't have this option (at least not with CSS versions before 3, CSS3 seems to have a text-justify property, but it's not well supported yet), so js solutions would be fine also.

Here's a script which can do it. It isn't pretty, but maybe you can hack it to meet your needs. (Updated to handle resizing)
function SplitText(node) {
var text = node.nodeValue.replace(/^\s*|\s(?=\s)|\s*$/g, "");
for (var i = 0; i < text.length; i++) {
var letter = document.createElement("span");
letter.style.display = "inline-block";
letter.style.position = "absolute";
letter.appendChild(document.createTextNode(text.charAt(i)));
node.parentNode.insertBefore(letter, node);
var positionRatio = i / (text.length - 1);
var textWidth = letter.clientWidth;
var indent = 100 * positionRatio;
var offset = -textWidth * positionRatio;
letter.style.left = indent + "%";
letter.style.marginLeft = offset + "px";
//console.log("Letter ", text[i], ", Index ", i, ", Width ", textWidth, ", Indent ", indent, ", Offset ", offset);
}
node.parentNode.removeChild(node);
}
function Justify() {
var TEXT_NODE = 3;
var elem = document.getElementById("character_justify");
elem = elem.firstChild;
while (elem) {
var nextElem = elem.nextSibling;
if (elem.nodeType == TEXT_NODE)
SplitText(elem);
elem = nextElem;
}
}
#character_justify {
position: relative;
width: 40%;
border: 1px solid red;
font-size: 32pt;
margin: 0;
padding: 0;
}
#character_justify * {
margin: 0;
padding: 0;
border: none;
}
<body onload="Justify()">
<p id="character_justify">
Something<br/> Like
<br/> This
</p>
</body>

The css only solution is text-justify: distribute https://www.w3.org/TR/css-text-3/#text-justify but the support is still very poor.
A small experiment using text-align-last: justify and adding spaces between letters.
div{
display:inline-block;
text-align: justify;
text-align-last: justify;
letter-spacing: -0.1em;
}
<div>
S o m e t h i n g<br>
l i k e<br>
t h i s
</div>

I know this is an old topic, but I faced this the other night. And found a suitable solution using tables.
Every letter shall be put into a <td> </td> I know it looks tedious, but if you wanna do this, it would be for a word or two, right? Or you always can use JS to fill it if is too much. However, this is only CSS and very versatile solution.
Using letter-spacing the letters get distributed properly. You should play around with it, depending on the width of the table.
#justify {
width: 300px;
letter-spacing: 0.5em;
}
<table id="justify">
<tbody>
<tr>
<td>J</td>
<td>U</td>
<td>S</td>
<td>T</td>
<td>I</td>
<td>F</td>
<td>Y</td>
</tr>
</tbody>
</table>
See the example here
Crossbrowser safe, virtually nothing shall differ. Is just CSS.
I used it in My website which is in english and spanish.
the subtitle under my name in spanish has an additional letter and it will step out the width. Using the tables explained above, it gets distributed to the same width automatically. Spacing it manually I'd had to define a whole condition for each language to go around that.

Here is an other aproach using a jQuery snippet I wrote for this question : Stretch text to fit width of div :
DEMO
HTML :
<div class="stretch">Something</div>
<div class="stretch">Like</div>
<div class="stretch">This</div>
jQuery :
$.fn.strech_text = function () {
var elmt = $(this),
cont_width = elmt.width(),
txt = elmt.html(),
one_line = $('<span class="stretch_it">' + txt + '</span>'),
nb_char = elmt.text().length,
spacing = cont_width / nb_char,
txt_width;
elmt.html(one_line);
txt_width = one_line.width();
if (txt_width < cont_width) {
var char_width = txt_width / nb_char,
ltr_spacing = spacing - char_width + (spacing - char_width) / nb_char;
one_line.css({
'letter-spacing': ltr_spacing
});
} else {
one_line.contents().unwrap();
elmt.addClass('justify');
}
};
$(document).ready(function () {
$('.stretch').each(function () {
$(this).strech_text();
});
});

Needed this too, so I've bundled the suggested technique in a simple to use jquery-plugin you can find here: https://github.com/marc-portier/jquery-letterjustify#readme.
It uses the same procedure at its core, and adds some options to tweak.
Comments welcome.

Found another way to achieve this with pure CSS, alas you need to spell out your words.
In my situation, this was the only solution that worked (some letters had classes), plus it also produced the straightest right-alignment among answers here, without using hacks.
.box {
width: min-content;
border: solid red;
}
.word {
display: flex;
justify-content: space-between;
}
<div class="box">
<div class="word">
<span>S</span>
<span>o</span>
<span>m</span>
<span>e</span>
<span>t</span>
<span>h</span>
<span>i</span>
<span>n</span>
<span>g</span>
<span> </span>
<span>w</span>
<span>i</span>
<span>c</span>
<span>k</span>
<span>e</span>
<span>d</span>
</div>
<div class="word">
<span>t</span>
<span>h</span>
<span>i</span>
<span>s</span>
<span> </span>
<span>w</span>
<span>a</span>
<span>y</span>
</div>
<div class="word">
<span>c</span>
<span>o</span>
<span>m</span>
<span>e</span>
<span>s</span>
</div>
</div>

Again, I know this is REALLY old, but why not just put a space between each letter and then text-align:justify? Then each letter would be regarded as a 'word' and justified accordingly

An alternate way to handle this might be to use the "vw" sizing unit. This unit type can be used in font size properties and represents a percent of the window's width.
Disclaimer: It is not exactly what you are looking for, but requires no scripting. It does adjust the text size, but will also scale to the width of your page.
For example,
.heading {
font-size: 4vw;
}
will make the width of one character in the current font 4% of the window width.
You could then use media queries if you wish to lock the font size to a minimum size based on the window's width.
#media only screen and (max-width: 768px) {
font-size: 2rem;
}
Use the browser inspector to play with the font-size property and tweak the value to what makes sense for your application.
The "vw" unit works in IE9+, iOS 8.3+ and Android 4.4+ and all other mainstream browsers. I wouldn't worry about the mobile support too much, as you can use media queries to put the right sizing for these devices as described above.
http://caniuse.com/#feat=viewport-units
https://css-tricks.com/viewport-sized-typography/
Viewport units are a powerful way to scale many different aspects of your site with little code.

I just made a JQuery script from table's Tony B approach.
Here is the JSFiddle https://jsfiddle.net/7tvuzkg3/7/
This script creates a table with each char in a row. This works with full sentence.
I'm not sure this is fully optimized.
justifyLetterSpacing("sentence");
function justifyLetterSpacing(divId) {
// target element
domWrapper = $('#' + divId).clone().html('');
// construct <td>
$('#' + divId).contents().each(function(index){
// store div id to td child elements class
var tdClass = divId ;
// split text
$textArray = $(this).text().split('');
// insert each letters in a <td>
for (var i = 0; i < $textArray.length; i++) {
// if it's a 'space', replace him by an 'html space'
if( $textArray[i] == " " ) {
$('<td>')
.addClass(tdClass)
.html(" ")
.appendTo(domWrapper);
}// if it's a char, add it
else{
$('<td>')
.addClass(tdClass)
.text($textArray[i])
.appendTo(domWrapper);
}
}
});
// create table
table =
$( "<table id='"+divId+"'/>" ).append(
$( "<tbody>" ).append(
$( "<tr>" ).append(
( domWrapper ).children('td')
)
)
);
// replace original div
$('#' + divId).replaceWith( table );
}
#sentence {
width : 100%;
background-color : #000;
color : #fff;
padding : 1rem;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="sentence">LOREM IPSUM DOLOR</div>

I usually try to write my answer on time. and this is exactly the same time (after 10 years) =)
myText.innerHTML = myText.textContent
.split(/\s+/g)
.map((line) => line.trim().split("").join(" "))
.join("<br>");
#myText {
display: inline-block;
text-align: justify;
text-align-last: justify;
letter-spacing: -0.125em;
font-family: sans-serif;
}
<div id="myText">Something like this</div>

Related

how to allocate space to a text as much as it needs and then the rest to the right side textInput

I have a Text component and a TextInput on a row beside each other, I what the Text to get space as much as it needs then leave the rest of the width of the device for the TextInput. How can I do that with flexbox? or any other tools? does Dimensions give me the length of a Text so that i can use it to allocate that to the text?
You should be able to get the gist of what is going on from the snippet below. Adapt this to your needs.
var containerWidth = document.getElementById("container").offsetWidth;
var labelWidth = document.getElementById("label").offsetWidth;
var inputWidth = containerWidth - labelWidth - 1 + "px"
document.getElementById("input").setAttribute( "style", `width: ${inputWidth}` );
#container {
width: 300px;
}
#label {
float: left;
}
#container, #input, #label {
box-sizing: border-box;
}
<div id="container">
<label for="input" id="label">Label</label>
<input id="input" type="text" />
</div>

CSS on :focus within child of contenteditable

I am trying to detect focus on a child element of a contenteditable element, for the purposes of pure CSS styling. (I know I could detect this with JS, add an extra class and do it that way, but that is so long-winded.)
Basically, I have something along the lines of:
<div contenteditable="true">
Some text <span class="edit">that</span> goes here.
</div>
I tried CSS along the lines of:
.edit:focus {
color: #FF0000;
}
I want that span to change colour when the caret enters it, but apparently the focus is only applied to the div set to contenteditable, not to any child thereof. I have tried applying a second contenteditable to the span, but besides being a horribly sloppy approach, it doesn't work anyway.
Is there a solution to this?
Because of the limitation that elements within a contenteditable element can't generally receive focus, I suggest faking it by adding a class to your <span> element when the selection is contained within it, which you can do by monitoring the selection for changes (you'll have to use mouse and keyboard events and polling for thoroughness in Firefox until the selectionchange event is implemented in that browser).
var selectionContainer = null;
function updateSelectionContainer() {
var newSelectionContainer = null;
var sel;
if (window.getSelection && (sel = window.getSelection()).rangeCount) {
newSelectionContainer = sel.getRangeAt(0).commonAncestorContainer;
// Ensure we have an element rather than a text node
if (newSelectionContainer.nodeType != 1) {
newSelectionContainer = newSelectionContainer.parentNode;
}
}
if (newSelectionContainer != selectionContainer) {
if (selectionContainer) {
selectionContainer.className = selectionContainer.className.replace(/ ?containsSelection/, "");
}
if (newSelectionContainer) {
newSelectionContainer.className +=
(newSelectionContainer.className ? " containsSelection" : "containsSelection");
}
selectionContainer = newSelectionContainer;
}
}
if ("onselectionchange" in document) {
document.onselectionchange = updateSelectionContainer;
} else {
var el = document.getElementById("editor");
el.onmousedown = el.onmouseup = el.onkeydown = el.onkeyup = el.oninput = updateSelectionContainer;
window.setInterval(updateSelectionContainer, 100);
}
div {
font-size: 200%;
}
.edit.containsSelection {
color: red;
font-weight: bold;
}
<div contenteditable="true" id="editor">
Some text <span class="edit">that</span> goes here.
</div>
My understanding is that the type of elements that can receive focus (automatically) is limited.
See SO Question
One option is to add tabindex to the span.
body {
font-size: 3rem;
}
div[contenteditable=true] .edit:focus {
color: #FF0000;
}
<div contenteditable="true">Some text <span class="edit" tabindex="0">that</span> goes here.</div>
:focus > .edit { color: #cc0000; }
<div contenteditable="true">Some text <span class="edit">that</span> goes here.</div>
<div contenteditable="true">Some text that goes here.</div>
Just add this instead of :focus. Fiddle.
.edit {
color: #f00;
}

Responsive headers and Magellan's fixed_top option

How do I define different fixed_top values within magellan's options, or css, to accommodate different header heights on various devices? My header height varies from 60px on medium to 120px on large.
The inline nature of Magellan's options trumps all css I use to change this within my media queries.
I have also tried interchange to swap out values to no avail.
I've had the same problem. Currently there is not really a way to fix this with solely Magellan, for it uses fixed parameters setting the offset. I've applied the following fix:
Remove fixed from the data-magellan-expedition attribute. Magellan will no longer handle the fixed-positioning
Add a 'break point' script that adds classes to the body which you can use to applied media queries. These break point classes are reusable for other applications as well. An example:
Let's say your html is like:
<div data-magellan-expedition class="your-magellan-nav-bar">
<div data-magellan-arrival="some-header">
<a href="#some-header">
</div>
</div>
<!-- ... -->
<h3 data-magellan-destination="some-header">Some Header</h3>
<a name="some-header></a>
Note the missing fixed in data-magellan-expedition="fixed".
Now add some JS to your document:
function activateScrollpoints(scrollPoints) {
var $window = $(window);
var $body = $(document.body);
var tId = null;
function onScroll() {
var windowScrollTop = $window.scrollTop();
scrollPoints.forEach(function(point) {
$body.toggleClass('break-'+point, windowScrollTop >= point);
});
tId = setTimeout(function() {
clearTimeout(tId);
window.requestAnimationFrame(onScroll);
}, 100);
}
window.requestAnimationFrame(onScroll);
}
activateScrollpoints([310, 500]);
The above will add a class break-310 once the user scrolls more than 310px and another class break-500 if the user scrolls 310px.
Now in your CSS you could do something like:
#media #{$medium-up} {
body.break-310 .your-magellan-nav-bar {
position: fixed;
top: 310px; /* Some value for your offset */
left: 0px;
}
}
#media #{$small-only} {
body.break-500 .your-magellan-nav-bar {
position: fixed;
top: 500px; /* Some value for your offset */
left: 0px;
}
}

Select odd even child excluding the hidden child

Line 3 is a hidden <div> . I don't want that one to be taken from the odd/even css rule.
What is the best approach to get this to work?
.hidden {display:none;}
.box:not(.hidden):nth-child(odd) { background: orange; }
.box:not(.hidden):nth-child(even) { background: green; }
<div class="wrap">
<div class="box">1</div>
<div class="box">2</div>
<div class="box hidden">3</div>
<div class="box">4</div>
<div class="box">5</div>
<div class="box">6</div>
<div class="box">7</div>
</div>
http://jsfiddle.net/k0wzoweh/
Note: There can be multiple hidden elements.
:nth-child() pseudo-class looks through the children tree of the parent to match the valid child (odd, even, etc), therefore when you combine it with :not(.hidden) it won't filter the elements properly.
Alternatively, we could fake the effect by CSS gradient as follows:
.hidden {display:none;}
.wrap {
line-height: 1.2em;
background-color: orange;
background-image: linear-gradient(transparent 50%, green 50%);
background-size: 100% 2.4em;
}
<div class="wrap">
<div class="box">xx</div>
<div class="box">xx</div>
<div class="box hidden">xx</div>
<div class="box">xx</div>
<div class="box">xx</div>
<div class="box">xx</div>
<div class="box">xx</div>
</div>
Pseudo-selectors don't stack, so your :not doesn't affect the :nth-child (nor would it affect :nth-of-type etc.
If you can resort to jQuery, you can use the :visible pseudo-selector there, although that's not a part of the CSS spec.
If you're generating the HTML and can change that, you can apply odd/even with logic at run-time, eg in PHP:
foreach ($divs AS $i => $div) {
echo '<div class="box ' . ($i % 2 ? 'even' : 'odd') . '">x</div>';
}
Even trying to do something tricky like
.box[class='box']:nth-of-type(even)
doesn't work, because the psuedo-selector doesn't even stack onto the attribute selector.
I'm not sure there's any way to do this purely with CSS - I can't think of any right now.
Here's a CSS-only solution:
.box {
background: orange;
}
.box:nth-child(even) {
background: green;
}
.box.hidden {
display: none;
}
.box.hidden ~ .box:nth-child(odd) {
background: green;
}
.box.hidden ~ .box:nth-child(even) {
background: orange;
}
<div class="wrap">
<div class="box">xx</div>
<div class="box">xx</div>
<div class="box hidden">xx</div>
<div class="box">xx</div>
<div class="box">xx</div>
<div class="box">xx</div>
<div class="box">xx</div>
</div>
Since my rows are being hidden with js, I found that the easiest approach for me was to just add an additional hidden row after each real row that I hide, and remove the hidden rows when I show the real rows again.
Hide the rows you want to hide calling .hide() for each table row, then call
$("tr:visible:even").css( "background-color", "" ); // clear attribute for all rows
$("tr:visible:even").css( "background-color", "#ddddff" ); // set attribute for even rows
Add your table name to the selector to be more specific. Using :even makes it skip the Header row.
As #Fateh Khalsa pointed out, I had a similar problem and since I was manipulating my table with JavaScript (jQuery to be precise), I was able to do the following:
(Note: This assumes use of JavaScript/jQuery which the OP did not state whether or not would be available to them. This answer assumes yes, it would be, and that we may want to toggle visibility of hidden rows at some point.)
Inactive records (identified with the CSS class "hideme") are currently visible.
Visitor clicks link to hide inactive records from the list.
jQuery adds "hidden" CSS class to "hideme" records.
jQuery adds additional empty row to the table immediately following the row we just hid, adding CSS classes "hidden" (so it doesn't show) and "skiprowcolor" so we can easily identify these extra rows.
This process is then reversed when the link is clicked again.
Inactive records (identified with the CSS class "hideme") are currently hidden.
Visitor clicks link to show inactive records from the list.
jQuery removes "hidden" CSS class to "hideme" records.
jQuery removes additional empty row to the table immediately following the row we just showed, identified by CSS class "skiprowcolor".
Here's the JavaScript (jQuery) to do this:
// Inactive Row Toggle
$('.toginactive').click(function(e) {
e.preventDefault();
if ($(this).hasClass('on')) {
$(this).removeClass('on'); // Track that we're no longer hiding rows
$('.wrap tr.hideme').removeClass('hidden'); // Remove hidden class from inactive rows
$('.wrap tr.skiprowcolor').remove(); // Remove extra rows added to fix coloring
} else {
$(this).addClass('on'); // Track that we're hiding rows
$('.wrap tr.hideme').addClass('hidden'); // Add hidden class from inactive rows
$('.wrap tr.hideme').after('<tr class="hidden skiprowcolor"></tr>');
// Add extra row after each hidden row to fix coloring
}
});
The HTML link is simple
Hide/Show Hidden Rows
scss for #tim answer's above, to keep class name changes to a minimum
$selector: "box";
$hidden-selector: "hidden";
.#{$selector} {
background: orange;
:nth-child(even) {
background: green;
}
&.#{$hidden-selector} {
display: none;
}
&.#{$hidden-selector} ~ {
.#{$selector} {
&:nth-of-type(odd) {
background: green;
}
&:nth-of-type(even) {
background: orange;
}
}
}
}
Another way, albeit on the fringe side, is to have an extra <tbody> and either move or copy rows there. Or, an extra div wrapper if using OPs example. Copying easiest of course in regards to restoring etc.
This approach can be useful in some cases.
Below is a simple example where rows are moved when filtered. And yes, it is ranking of stripper names, found it fitting as we are talking stripes ... hah
const Filter = {
table: null,
last: {
tt: null,
value: ''
},
name: function (txt) {
let tb_d = Filter.table.querySelector('.data'),
tb_f = Filter.table.querySelector('.filtered'),
tr = tb_d.querySelectorAll('TR'),
f = 0
;
tb_f.innerHTML = '';
if (txt.trim() == '') {
tb_d.classList.remove('hide');
} else {
txt = txt.toLowerCase();
for (let i = 0; i < tr.length; ++i) {
let td = tr[i].querySelectorAll('TD')[1];
if (td.textContent.toLowerCase().includes(txt)) {
tb_f.appendChild(tr[i].cloneNode(true));
f = 1;
}
}
if (f)
tb_d.classList[f ? 'add' : 'remove']('hide');
}
},
key: function (e) {
const v = e.target.value;
if (v == Filter.last.value)
return;
Filter.last.value = v;
clearTimeout(Filter.last.tt);
Filter.last.tt = setTimeout(function () { Filter.name(v); }, 200);
}
};
Filter.table = document.getElementById('table');
Filter.table.addEventListener('keyup', Filter.key);
table {
width: 200px;
border: 3px solid #aaa;
}
tbody tr { background: #e33; }
tbody tr:nth-child(even) { background: #e3e; }
.hide { display: none; }
<table id="table">
<thead>
<tr><th></th><th><input type="text" id="filter" data-keyup="filter" /></th></tr>
<tr><th>#</th><th>Name</th></tr>
</thead>
<tbody class="filtered">
</tbody>
<tbody class="data">
<tr><td>1</td><td>Crystal</td></tr>
<tr><td>2</td><td>Tiffany</td></tr>
<tr><td>3</td><td>Amber</td></tr>
<tr><td>4</td><td>Brandi</td></tr>
<tr><td>5</td><td>Lola</td></tr>
<tr><td>6</td><td>Angel</td></tr>
<tr><td>7</td><td>Ginger</td></tr>
<tr><td>8</td><td>Candy</td></tr>
</tbody>
</table>
You can use another type of CSS selector: tbody > tr:nth-of-type(odd)
to only target tr nodes, and then, instead of using class names to hide the rows, simply wrap them with some element (which hides them), so the CSS selector would only match odd table rows:
const searchElem = document.querySelector('input');
const tableElem = document.querySelector('table');
const tableBody = document.querySelector('tbody');
function search() {
const str = searchElem.value.toLowerCase();
const rows = tableElem.querySelectorAll('tr');
// remove previous wrappers
// https://stackoverflow.com/a/48573634/104380
tableBody.querySelectorAll('div').forEach(w => {
w.replaceWith(...w.childNodes)
});
// create a wrapper which hides its content:
const wrapper = document.createElement("div");
wrapper.setAttribute('hidden', true);
rows.forEach(function(row){
const text = row.textContent.toLowerCase();
if (str.length && !text.includes(str)) {
// replace row with wrapper and put the row inside it
row.replaceWith(wrapper);
wrapper.appendChild(row);
}
});
}
searchElem.addEventListener('keyup', search);
tbody > tr:nth-of-type(odd) {
background: pink
}
<input type="search" placeholder="search">
<table>
<tbody>
<tr><td>Apple<td>220
<tr><td>Watermelon<td>465
<tr><td>Orange<td>94
<tr><td>Pear<td>567
<tr><td>Cherry<td>483
<tr><td>Strawberry<td>246
<tr><td>Nectarine<td>558
<tr><td>Grape<td>535
<tr><td>Mango<td>450
<tr><td>Blueberry<td>911
<tr><td>Pomegranate<td>386
<tr><td>Carambola<td>351
<tr><td>Plum<td>607
<tr><td>Banana<td>292
<tr><td>Raspberry<td>912
<tr><td>Mandarin<td>456
<tr><td>Jackfruit<td>976
<tr><td>Papaya<td>200
<tr><td>Kiwi<td>217
<tr><td>Pineapple<td>710
<tr><td>Lime<td>983
<tr><td>Lemon<td>960
<tr><td>Apricot<td>647
<tr><td>Grapefruit<td>861
<tr><td>Melon<td>226
<tr><td>Coconut<td>868
<tr><td>Avocado<td>385
<tr><td>Peach<td>419
</tbody>
</table>

Select all by class except last

I have a series of div and I would like to select all p tags with enable class except the last inside each div in order to apply a specific style in css. The content are dynamically generated and may vary depending on the user.
In the example below, I would like to apply this style to the first two p inside the first div and none inside the other. I'm sure it's pretty easy but I don't find any solution to solve it.
<div>
<p class="enable"></p>
<p class="enable"></p>
<p class="enable"></p>
<p class="disable"></p>
</div>
<div>
<p class="enable"></p>
<p class="disable"></p>
<p class="disable"></p>
<p class="disable"></p>
</div>
Thanks for your help.
If the p elements are confined to four per div you can use the nth-child pseudo selector.
p.enable:nth-child(-n+2)
{
background: #0f0;
}
p.disable
{
background: #eee;
}
http://jsfiddle.net/Kyle_Sevenoaks/vw4wQ/
I have a Javascript solution for you:
var divs = document.getElementsByTagName("div"), paras, i, j;
for(i = 0; i < divs.length; i++) {
paras = divs[i].getElementsByTagName("p");
for(j = 0; j < paras.length-1; j++) {
if(paras[j+1].className === "disable")
break;
// apply your custom code here
paras[j].style.background = "#FF0000";
}
}
Check this solution in this jsFiddle

Resources