How to get the text-overflow formatted text from a DIV - javascript

[UPDATE]: I later on figure out a stupid way to handle it(not always works, but most time):
Style:
#txt_ruler {
position: fixed;
top: -100px;
left:-100px;
visibility: hidden;
}
HTML:
<div id="txt_ruler"></div>
JS:
String.prototype.visualLength = function(style) {
var ruler = $("#txt_ruler")[0];
if(style["font-weight"]){
d3.select("#txt_ruler").style("font-weight", style["font-weight"]);
}else {
d3.select("#txt_ruler").style("font-weight", null);
}
d3.select("#txt_ruler").style("font-size", style["font-size"]);
ruler.innerHTML = this;
return ruler.offsetWidth;
}
String.prototype.visualText = function(style, width) {
var l = this.visualLength(style);
if(l<=width){
return this.toString();
}else {
var percent = width/l;
var breakIndex = Math.floor(this.length*percent);
var tmpStr = this.substring(0, breakIndex+1)+ "…";
while(breakIndex>=0 && tmpStr.visualLength(style)>width){
breakIndex--;
tmpStr = this.substring(0, breakIndex+1)+ "...";
}
return tmpStr;
}
}
All:
What I want to do is get the characters left if there is a container overflow.
The way I am thinking about(but still not sure how to do that) currently is to use:
#ofc {
font-size:20px;
display: inline-block;
width:200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
<div id="ofc">A very1 very2 very3 very4 very5 very6 very7 very8 Long TEXT</div>
What shown on the page is:
A very1 very2 very3 ...
But my current question is:
How can I use jQuery or D3.js or JS to get this crop text with 3 dots, but not the whole?
OR
According to what my purpose, is there a mathematical way to apply specific font-size to it to actually calculate what the text left in a DIV with specific width?

Related

MathJax in div incorrect height

I am trying to figure out why MathJax render block gives me the wrong height for the div. The code is
<div class="text-field" id='inner-text'>\(\sqrt{b^{a}\frac{\partial y}{\partial x}}\)</div>
with CSS
.text-field {
display: inline-block;
overflow: hidden;
max-width: 15em;
}
When the following JS snippet is run
MathJax.typeset();
let text = document.getElementById("inner-text");
console.log(text.clientHeight,
text.offsetHeight,
text.getBoundingClientRect().height,
window.getComputedStyle(text).getPropertyValue('height'));
The console gives
41 41 41.25 "41.25px"
However, in inspect elements:
The actual height does not agree with any of height options accessible via JS. What is going on and how should can a get an accurate height value?
The problem is that it takes MathJax time to create the visualization. The idea of the solution I made is to give time to MathJax and when it is ready then we take the size of the element.
I made two versions of the code. Both work correctly in Firefox, Chrome, Edge... etc.
Option 1:
The script waits for MathJax to load then gives it another 100ms to complete and then takes the size of the inner-text
var checkEx = setInterval(function () {
let wrap = document.getElementById("inner-text");
var text = wrap.getElementsByClassName('MathJax')[0];
if (text) {
setTimeout(() => {
console.log(wrap.getBoundingClientRect().height, wrap.getBoundingClientRect().width);
}, 100);
clearInterval(checkEx);
}
}, 100);
.text-field {
display: inline-block;
overflow: hidden;
max-width: 15em;
}
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax#3/es5/tex-mml-chtml.js"></script>
<div class="text-field" id='inner-text'>\(\sqrt{b^{a}\frac{\partial y}{\partial x}}\)</div>
Option 2
The script waits for MathJax to load then begins to take the size of the element. When the size stops changing... return the size of the inner-text
var elhg;
var elwg;
var checkEx = setInterval(function () {
let wrap = document.getElementById("inner-text");
var text = wrap.getElementsByClassName('MathJax')[0];
if (text) {
elHeight = wrap.getBoundingClientRect().height;
elWidth = wrap.getBoundingClientRect().width;
if (elhg === elHeight && elwg === elWidth) {
console.log(elHeight, elWidth);
clearInterval(checkEx);
}
elhg = elHeight;
elwg = elWidth;
}
}, 100);
.text-field {
display: inline-block;
overflow: hidden;
max-width: 15em;
}
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax#3/es5/tex-mml-chtml.js"></script>
<div class="text-field" id='inner-text'>\(\sqrt{b^{a}\frac{\partial y}{\partial x}}\)</div>

How to move nodes from a contentEditable div to another dynamically when its content exceed a x height?

I am trying to prototype a simple wyswyg that emulate the concept of A4 pages using contentEditable divs.
So my current code is this:
HTML:
<div id="editor">
<div contenteditable="true" class="page" id="page-1">
<b>hello</b>
</div>
</div>
CSS:
#editor{
background-color: gray;
border: 1px black;
padding: 1em 2em;
}
.page{
background-color: white;
border: solid black;
padding: 1em 2em;
width:595px;
height:841px;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal;
}
JS:
//force br
document.execCommand("DefaultParagraphSeparator", false, "br");
const a4 = {
height: 841,
width: 595
};
document.getElementById('editor').addEventListener('input', function(e) {
let getChildrenHeight = function(element) {
total = 0;
if (element.childNodes) {
for (let child of element.childNodes) {
switch (child.nodeType) {
case Node.ELEMENT_NODE:
total += child.offsetHeight;
break;
case Node.TEXT_NODE:
let range = document.createRange();
range.selectNodeContents(child);
rect = range.getBoundingClientRect();
total += (rect.bottom - rect.top);
break;
}
}
}
return total;
};
let pages = document.getElementsByClassName('page');
for (let i in pages) {
let page = pages[i];
//remove empty page
if (page.offsetHeight == 0 && i > 1) {
page.remove();
}
let childrenHeight = getChildrenHeight(page);
while (childrenHeight > a4.height) {
//recursively try to fit elements on max size
//removing/pushing excedents elements to the next div (aka page)
let excedents = [];
let children = page.childNodes;
let children_length = children.length - 1;
let backup = children[children_length].cloneNode(true);
children[children_length].remove();
if (pages.item(i + 1) === null) {
var newPage = page.cloneNode(true);
newPage.innerHTML = '';
newPage.appendChild(backup);
page.parentNode.appendChild(newPage);
} else {
page.item(i + 1).insertBefore(backup, page.item(i + 1).childNodes[0]);
}
//console.log(children[i].innerHTML);
}
}
});
Unfortunately, the result is not as I was expecting.
When the height of one page is exceeded, all content from the first page is removed, not like I would like:
the excess to be moved to next page.
and when a page is abscent of children, been removed.
Something like a very very primitive Microsoft Word multipages editor.
How to do that?
Thanks in advance
Celso
Your code is a good start, but there are a couple off things to fix:
You are a trying to iterate trough a HTMLCollection with your for..in loop, which will access length, item and namedItem in the collection (just try for(let i in document.getElementsByClassName('page')) console.log(i); in the console)
You're trying to remove empty pages when offsetHeight is 0, instead try childrenHeight
you can exchange the while loop with an if statement
you also have to check if there is enough sapce on the current page, to pull back lines from the next one
also, you have to manually handle cursor position on page breaks
I made a codepen to demonstrate the changes I suggested. It is far from perfect, but handles page removals and excess removal.

Truncate text and restore it as needed

My Codepen
if("matchMedia" in window) {
if(window.matchMedia("(max-width: 533px)").matches) {
$('.title').each(function() {
var theContent = $(this).text();
if (theContent.length >= 35) {
var n = theContent.substring(0, 35);
$(this).html(n + '...');
}
});
} else {
// ??
}
}
Hello,
I have to create a function that must make sure to truncate text too large when I pass on a mobile resolution. The first part (truncation) works, but I can not finish it to restore all the titles on my page when the resolution goes above 533px.
Can someone give me a track? Or give me a better method to do if there is better?
Thank you very much.
How about pure CSS?
.title {
width: 50%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
<div class="title">
I am a somewhat long title, that will not fit on all screens
</div>
I would highly recommend that you do it with purely css, but if you really need to do it, you can use attributes or data attribute to hold the original text.
$("#test").on("change", function() {
if (this.checked) {
$('.title').text(function(i, orgText) {
var elem = $(this);
if (!elem.data("orgText")) {
elem.data("orgText", orgText);
}
return (orgText.length >= 35) ? orgText.substring(0, 35) + "…" : orgText;
});
} else {
$('.title').text(function(i, text) {
return $(this).data("orgText") || text;
});
}
}).change();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h3 class="title">12345678901234567890123456789012345678901234567890</h3>
<h3 class="title">1234567890123456789012345678901234567890</h3>
<h3 class="title">1234567890</h3>
<input type="checkbox" id="test" checked>
<label for="test">shrink</label>

Dynamically alter contents of a div and append to another div

So basically I have this div in body
<body>
<div id="main_content"></div>
</body>
Now I am downloading some data from the internet (a set of boolean data) and I have this div template. Let's say the data is (true, false, true). Then for each data I want to alter the template div. For example: first one is true so inside the template div I will change the sub1 div's height to 40 px; if it's false, I'd change sub2 div's height to 40 px; and then I'd append this modified template div to main_content div
Template div:
.child{
width:300px;
height:auto;
}
.sub1{
width:300px;
height:20px;
background-color:#0FF;
}
.sub2{
width:300px;
height:20px;
background-color:#F0F;
}
<div class="child">
<div class="sub1"></div>
<div class="sub2"></div>
</div>
After all this this should be the final output of main_content div
What would be the easiest way of doing this using HTML/CSS/JS.
Thanks
Short answer: Here is a codepen
Long answer:
I would use js to dynamically generate your template div:
function makeTemplateDiv() {
var child = document.createElement('div');
child.className = "child"
var sub1 = document.createElement('div');
sub1.className = "sub1"
var sub2 = document.createElement('div');
sub2.className = "sub2"
child.appendChild(sub1);
child.appendChild(sub2);
return child;
}
Then make a css class for a taller 40 px
.taller {
height: 40px;
}
Then use js to to alter your template based on a passed in value
function alterTemplateDiv(value) {
var template = makeTemplateDiv();
if(value) {
template.getElementsByClassName("sub1")[0].className += " taller";
} else {
template.getElementsByClassName("sub2")[0].className += " taller";
}
return template;
}
Then use js to pass in your array of values, make the divs, and append them
function appendDivs(arrayOfValues) {
var mainDiv = document.getElementById("main_content");
for(var i = 0; i < arrayOfValues.length; i++) {
mainDiv.appendChild(alterTemplateDiv(arrayOfValues[i]));
}
}
This kind of question begs for a million different types of answers, but I think this generally keeps with most best practices for front end coding without the use of a framework:
// Self-invoking function for scoping
// and to protect important global variables from other script changes
// (The variable references can be overwritten)
(function (window, document) {
var templateText,
generatedEl,
topEl,
bitArray;
// Data
bitArray = [true, false, true];
// Get template text
templateText = document.getElementById('my-template').text.trim();
// Loop through your T / F array
for (var i = 0, l = bitArray.length; i < l; i++) {
// Create a DIV and generate HTML within it
generatedEl = document.createElement('div');
generatedEl.innerHTML = templateText;
// Modify the new HTML content
topEl = generatedEl.getElementsByClassName('child')[0];
topEl.className += bitArray[i] ? ' typeA' : ' typeB' ;
// Insert generated HTML (assumes only one top-level element exists)
document.getElementById('my-container').appendChild(generatedEl.childNodes[0]);
}
})(window, document);
.child {
width: 300px;
height: auto;
}
/* For true */
.child.typeA > .sub1 {
width: 300px;
height: 40px;
background-color: #0FF;
}
.child.typeA > .sub2 {
width: 300px;
height: 20px;
background-color: #F0F;
}
/* For false */
.child.typeB > .sub1 {
width: 300px;
height: 20px;
background-color: #0FF;
}
.child.typeB > .sub2 {
width: 300px;
height: 40px;
background-color: #F0F;
}
<!-- Container -->
<div id="my-container">
<!-- HTML Template -->
<script id="my-template" type="text/template">
<div class="child">
<div class="sub1"></div>
<div class="sub2"></div>
</div>
</script>
</div>
Note that the HTML content, JavaScript code and CSS are all kept very separated. This is based on the concepts of "Separation of Concerns" and "Unobtrusive JavaScript". I invite you to read up on them if you haven't already. Also, front end templating can be used for dynamic content like I did here, but I would recommend doing templating on the back end when you can. It works better for SEO purposes.
jQuery makes it easier to manipulate the DOM, so here is another solution for your problem:
var data = [true, false, true];
for (i=0; i<data.length; i++) {
var height1;
var height2;
if (data[i] == true) {
height1 = 40;
height2 = 20;
}
else {
height1 = 20;
height2 = 40;
}
var div1 = document.createElement("div");
$(div1).toggleClass("sub1")
.height(height1)
.appendTo("#main_content");
var div2 = document.createElement("div");
$(div2).toggleClass("sub2")
.height(height2)
.appendTo("#main_content");
}

Filling div using letter-spacing

The problem I'm having is filling a div with text using letter-spacing. The main issue is, I don't know the width of the div.
First I was thinking using, text-align= justify, but since that I've been running in the dark and got no clue to how to solve this. I'm guessing some scripting magic might do the trick.
An imgur link giving you an idea what I mean:
<div id="container">
<h1>Sample</h1>
<p>Another even longer sample text</p>
</div>
Here is a link showcasing an example; JSfiddle.
Based the comment of the poster it seems JavaScript is no problem. Here's a possible approach to solve the problem with jQuery:
JSFiddle 1
function dynamicSpacing(full_query, parent_element) {
$(full_query).css('letter-spacing', 0);
var content = $(full_query).html();
var original = content;
content = content.replace(/(\w|\s)/g, '<span>$1</span>');
$(full_query).html(content);
var letter_width = 0;
var letters_count = 0;
$(full_query + ' span').each(function() {
letter_width += $(this).width();
letters_count++;
});
var h1_width = $(parent_element).width();
var spacing = (h1_width - letter_width) / (letters_count - 1);
$(full_query).html(original);
$(full_query).css('letter-spacing', spacing);
}
$(document).ready(function() {
// Initial
dynamicSpacing('#container h1', '#container');
// Refresh
$(window).resize(function() {
dynamicSpacing('#container h1', '#container');
});
});
Update
Small tweak for when the wrapper gets too small: JSFiddle 2
Another solution if you don't have to be semantic (because you will get many spans), I mean if you need only the visual result, is to use flexbox.
So you have your <div id="#myText">TEXT 1</div>
We need to get this:
<div id="#myText">
<span>T</span>
<span>E</span>
<span>X</span>
<span>T</span>
<span> </span>
<span>1</span>
</div>
So then you can apply CSS:
#myText {
display: flex;
flex-direction: row;
justify-content: space-between;
}
In order to transform the text to span you can use jQuery or whatever. Here with jQuery:
var words = $('#myText').text().split("");
$('#myText').empty();
$.each(words, function(i, v) {
if(v===' '){
$('#myText').append('<span> </span>');
} else {
$('#myText').append($("<span>").text(v));
}
});
For better results remove put letter-spacing: 0 into #myText so any extra spacing will be applied.
This is obviously evil, but since there is no straight forward way to do it with just css, you could do: demo
HTML:
<div>text</div>
CSS:
div, table {
background: yellow;
}
table {
width: 100%;
}
td {
text-align: center;
}
JS:
var text = jQuery("div").text();
var table = jQuery("<table><tr></tr></table>").get(0);
var row = table.rows[0];
for (var i = 0; i < text.length; i++) {
var cell = row.insertCell(-1);
jQuery(cell).text(text[i]);
}
jQuery("div").replaceWith(table);
This may help:
function fill(target) {
var elems = target.children();
$.each(elems, function(i,e) {
var x = 1;
var s = parseInt($(e).css('letter-spacing').replace('px',''));
while(x == 1) {
if($(e).width() <= target.width() - 10) {
s++;
$(e).css('letter-spacing', s+'px');
} else {
x = 0;
}
}
});
}
fill($('#test'));
Note: If letter spacing is : 0 then you don't have to use replace method. Or you can add letter-spacing:1px; to your css file.
For avoiding overflow, always give minus number to parent element's height for correct work.
An other approach I wrote for this question Stretch text to fit width of div. It calculates and aplies letter-spacing so the text uses the whole available space in it's container on page load and on window resize :
DEMO
HTML :
<div id="container">
<h1 class="stretch">Sample</h1>
<p class="stretch">Another even longer sample text</p>
</div>
jQuery :
$.fn.strech_text = function(){
var elmt = $(this),
cont_width = elmt.width(),
txt = elmt.text(),
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();
});
$(window).resize(function () {
$('.stretch').each(function(){
$(this).strech_text();
});
});
});
CSS :
body {
padding: 130px;
}
#container {
width: 100%;
background: yellow;
}
.stretch_it{
white-space: nowrap;
}
.justify{
text-align:justify;
}

Categories

Resources