Dynamically expand <input> element with text - javascript

I feel like this should be easy. I've been searching around / trying things out for at least a week, and still to no avail.
I'd like an <input> element with placeholder text. I'd like the element to only be as wide as the placeholder text. When you click into the <input>, I'd like the width to remain the same. As you type, if your input text exceeds the width of the original placeholder text (and therefore the <input> element itself), I'd like the <input> to expand to accommodate the text.
Thoughts, SO?

Here is one attempt (in vanilla javascript) at a working solution which takes into account different font sizes, different character widths etc.:
function reviewWidth(startWidth) {
/* Grab various elements */
var hiddenSpan = document.getElementsByClassName('hidden')[0];
var inputValue = document.getElementsByTagName('input')[0].value;
/* Update text content of hiddenSpan */
hiddenSpan.innerHTML = inputValue;
/* Update <input> width */
var hiddenSpanStyles = getComputedStyle(hiddenSpan);
var newWidth = parseInt(hiddenSpanStyles.getPropertyValue('width'));
if (newWidth > startWidth) {
input.style.width = newWidth + 'px';
}
else {
input.style.width = startWidth + 'px';
}
}
/* Grab various elements */
var body = document.getElementsByTagName('body')[0];
var input = document.getElementsByTagName('input')[0];
var placeholder = input.getAttribute('placeholder');
/* Create hiddenSpan */
var hiddenSpan = document.createElement('span');
var placeholderText = document.createTextNode(placeholder);
hiddenSpan.appendChild(placeholderText);
/* Style hiddenSpan */
var inputStyles = getComputedStyle(input);
hiddenSpan.style.fontFamily = inputStyles.getPropertyValue('font-family');
hiddenSpan.style.fontSize = inputStyles.getPropertyValue('font-size');
hiddenSpan.style.borderLeftWidth = inputStyles.getPropertyValue('border-left-width');
hiddenSpan.style.paddingLeft = inputStyles.getPropertyValue('padding-left');
hiddenSpan.style.paddingRight = inputStyles.getPropertyValue('padding-right');
hiddenSpan.style.borderRightWidth = inputStyles.getPropertyValue('border-right-width');
hiddenSpan.style.display = 'inline-block';
hiddenSpan.style.opacity = '0';
hiddenSpan.classList.add('hidden');
/* Add hiddenSpan to document body */
body.appendChild(hiddenSpan);
/* Initialise <input> width */
var hiddenSpanStyles = getComputedStyle(hiddenSpan);
var startWidth = parseInt(hiddenSpanStyles.getPropertyValue('width'));
input.style.width = startWidth + 'px';
/* Run reviewWidth() function once */
if (input.value != '') {
reviewWidth(startWidth);
}
/* Add Event Listener to <input> to trigger reviewWidth() function */
input.addEventListener('input',function(){reviewWidth(startWidth);},false);
input {
font-family: arial, sans-serif;
font-size: 0.8em;
padding: 2px;
}
<input type="text" placeholder="Example Placeholder" />

I added some minor improvements to #Rounin's answer to make my own attempt:
Parsing consecutive spaces in HTML compresses to one space unless they're non-breaking, so I mapped to &nbsp. In addition, I mapped < to < to escape HTML. The reason I did not use textContent instead of innerHTML was because there's no way to inject non-breaking spaces into textContent.
The hidden span is no longer part of the DOM when it is not needed, so it doesn't trigger the parent element to scroll when it's too wide.
the pixel values for width are actually floats, not integers, so using parseFloat instead of parseInt makes the <input/> width more precise.
I trimmed down some of the code to reduce verbosity while trying to maintain readability.
/* Set up */
var regExp = /[ <]/g;
function replacer(c) {
return {
' ': ' ',
'<': '<'
}[c];
}
/* Grab various elements */
var body = document.querySelector('body');
var input = document.querySelector('input');
var placeholder = input.getAttribute('placeholder');
/* Create hiddenSpan */
var hiddenSpan = document.createElement('span');
hiddenSpan.innerHTML = placeholder.replace(regExp, replacer);
/* Style hiddenSpan */
var inputStyles = getComputedStyle(input);
['font', 'padding', 'border', 'display'].forEach(function(prop) {
hiddenSpan.style[prop] = inputStyles.getPropertyValue(prop);
});
hiddenSpan.style.visibility = 'hidden';
hiddenSpan.style.pointerEvents = 'none';
var hiddenSpanStyles = getComputedStyle(hiddenSpan);
/* Initialise <input> width */
body.appendChild(hiddenSpan);
var startWidth = parseFloat(hiddenSpanStyles.getPropertyValue('width'));
body.removeChild(hiddenSpan);
function reviewWidth() {
var inputValue = input.value;
/* Update text content of hiddenSpan */
hiddenSpan.innerHTML = inputValue.replace(regExp, replacer);
body.appendChild(hiddenSpan);
/* Update <input> width */
var newWidth = parseFloat(hiddenSpanStyles.getPropertyValue('width'));
body.removeChild(hiddenSpan);
if (newWidth > startWidth) {
input.style.width = newWidth + 'px';
} else {
input.style.width = startWidth + 'px';
}
}
reviewWidth();
/* Add Event Listener to <input> to trigger reviewWidth() function */
input.addEventListener('input', reviewWidth, false);
input {
font-family: arial, sans-serif;
font-size: 0.8em;
padding: 2px;
}
<input type="text" placeholder="Example Placeholder" />
Feel free to leave comments if you have questions or suggestions to improve this answer.

Related

Problem with onclick firing to incrementally increase the size of each image in a for loop

I am trying to write a script in the head element of my web page where a loop of 10 images will successively increase by 5 pixels each when any one of the looped images is clicked. When the onclick fires, it does create a loop that correctly increases the size for each image, but unfortunately this output gets added to the end of the original loop instead of changing it.
In my head element script, I first tried to use getDocumentById(), then switched to passing the "this" reference to the function, but came up with the same result. I also tried to use addEventListener(), but this didn't work either.
In the head element:
<script>
function growingPumpkins(e) {
for (var i = 0; i < 10; i++) {
e.innerHTML += `<img src='bandit.png' style="width:${50 + i * 5}px; height:${50 + i * 5}px "/>`;
}
}
</script>
In the body element:
<section>
<h2>Growing Pumpkins</h2>
<p id="smashingPumpkins" onclick="growingPumpkins(this)" ></p>
<script>
for (var i = 0; i < 10; i++) {
document.getElementById("smashingPumpkins").innerHTML += "<img src='bandit.png' />";
}
</script>
</section>
I wanted to initially create an HTMLCollection in the head element to achieve the desired result, but wasn't able to get any output when trying that. Right now, I still get the onclick loop concatenated to the original loop.
In order to use Template Literals assertions ${foo} you need backticks, not quotes
`Text ${foo} text`
e.innerHTML += is used to append to the current HTML of an element, not to increase the size of an element.
Instead:No need for for loops. Use document.querySelectorAll() and NodeList.forEach() with Element.addEventListener() to attach a desired function to the Event handler
const grow = ev => {
const el = ev.currentTarget;
el._w = el._w ? el._w + 10 : 40;
el.style.width = `${el._w}px`;
};
document.querySelectorAll(".grow").forEach(el => el.addEventListener('click', grow));
.grow {
display: inline-block;
vertical-align: middle;
width: 30px;
transition: width 0.3s;
cursor: pointer;
}
<img class="grow" src="https://i.imgur.com/GDUJj6t.jpg">
<img class="grow" src="https://i.imgur.com/GDUJj6t.jpg">
Increment size of multiple images on parent click:
const el_backyard = document.querySelector("#backyard");
const tot = 5; // total images
const min_size = 30; // px
const step = 10; // increase by 10 px
// PLANT
for (let i=1; i<=tot; i++) {
const el = new Image();
el.src = "https://i.imgur.com/GDUJj6t.jpg";
el.style.width = `${step * i}px`;
el_backyard.append(el);
}
// GROW
const grow = ev => {
el_backyard.querySelectorAll('img').forEach(img => {
img.style.width = `${img.offsetWidth + step}px`;
});
};
el_backyard.addEventListener('click', grow);
#backyard > * {
display: inline-block;
vertical-align: middle;
transition: width 0.3s;
cursor: pointer;
}
<div id="backyard"></div>

Show/Hide Only Working in Chrome (jQuery)

This question has been asked before, in different context (works in Firefox, not Chrome).
My show/hide functions are not working in Firefox, but they are working fine in Chrome. Technically, it works ONCE in Firefox, then it will not work again until I refresh the page. Again, works fine in Chrome and Internet Explorer.
jQuery v1.11.1
Firefox v40.0.3
Chrome v45.0.2454.93
**** UPDATE: **** I fixed the problem: I replaced the code under "jQuery" with the following code.
/* Show Product Overlay */
function enlargeProduct() {
$('#productOverlay').toggle('fast', 'linear', 'closeProductOverlay()');
}
function closeProductOverlay() {
/* Hide Product Overlay */
$(document).on('click', function (event) {
if (!$(event.target).closest('#clickControl').length) {
$('#productOverlay').hide();
}
});
}
jQuery -- OLD CODE that didn't work:
function enlargeProduct() {
/* Show Hidden Div Overlay */
$('#productOverlay').show();
};
function closeProductOverlay() {
/* Hide Product Overlay */
$(document).on('click', function (event) {
if (!$(event.target).closest('#clickControl').length) {
$('#productOverlay').hide();
}
});
}
HTML to Show/Hide
<!-- Product Overlay -->
<div id="productOverlay" onclick="closeProductOverlay()">
<div id="mobiPadding">
<div id="productContainer">
<div id="clickControl">
<input type="number" onchange="addQuantity()" value="1" min="1" max="99" />
<input type="button" class="btn btn-primary" onclick="addToCart()" value="Add to Cart" />
</div>
</div>
</div>
</div>
HTML Control - This is the div container into which I populate the HTML
<div class="item-container container">
</div>
Javascript to Populate HTML Control
var item = document.getElementsByClassName("item-container")[0];
var items = json.items;
for (var i = 0; i < items.length; i++) {
/* Outermost Div - For Bootstrap Formatting */
var div = document.createElement("div");
var divClass = document.createAttribute("class");
divClass.value = "col-md-3";
div.setAttributeNode(divClass);
item.appendChild(div);
/* Center Div - For adding Padding to each box */
var div2 = document.createElement("div");
var divClass = document.createAttribute("class");
divClass.value = "itemDiv";
div2.setAttributeNode(divClass);
div.appendChild(div2);
/* Innermost Div - For Adding Hover Effects (Not visible, functionality only) */
var hoverDiv = document.createElement("div");
var divClass = document.createAttribute("class");
divClass.value = "hoverDiv";
hoverDiv.setAttributeNode(divClass);
divClass = document.createAttribute("onmouseover");
divClass.value = "onMouseOver(" + i + ")";
hoverDiv.setAttributeNode(divClass);
divClass = document.createAttribute("onmouseout");
divClass.value = "onMouseOut(" + i + ")";
hoverDiv.setAttributeNode(divClass);
/* Add new ID iteration to identify selected product for button toggle */
var divId = document.createAttribute("id");
divId.value = "btn" + i;
hoverDiv.setAttributeNode(divId);
div2.appendChild(hoverDiv);
/* Add clearfix to clear formatting after each fourth iteration */
if (i >= 0) {
var j = i - 3;
if (j % 4 == 0) {
var clearfix = document.createElement("div");
var fixClass = document.createAttribute("class");
fixClass.value = "clearfix";
clearfix.setAttributeNode(fixClass);
item.appendChild(clearfix);
}
}
/* Title */
var h2 = document.createElement("h4");
h2.innerHTML = items[i].title.text;
hoverDiv.appendChild(h2);
/* Class */
var titleClass = document.createAttribute('class');
titleClass.value = "titleClass";
h2.setAttributeNode(titleClass);
/* Image */
var img = document.createElement("img");
var src = document.createAttribute('src');
src.value = items[i].image.text;
img.setAttributeNode(src);
hoverDiv.appendChild(img);
/* Class */
var imageClass = document.createAttribute('class');
imageClass.value = "imageClass";
img.setAttributeNode(imageClass);
/* Description */
var p = document.createElement("p");
p.innerHTML = items[i].description.text;
hoverDiv.appendChild(p);
/* Class */
var descClass = document.createAttribute('class');
descClass.value = "descriptionClass";
p.setAttributeNode(descClass);
/* Price */
var price = document.createElement("p");
price.innerHTML = items[i].price.text;
hoverDiv.appendChild(price);
/* Class */
var priceClass = document.createAttribute('class');
priceClass.value = "priceClass";
price.setAttributeNode(priceClass);
/* Hidden Click to Enlarge Product Button - Show/Hide on Hover*/
var productButton = document.createElement("div");
productButton.innerHTML = '<input id="productBtn' + i + '" onclick="enlargeProduct()" class="productBtn btn btn-primary" value="Click to Enlarge"/>';
hoverDiv.appendChild(productButton);
};
/* DOM Event Listeners - Handle Mouse Over/Out to Toggle Button and Product Overlays */
function onMouseOver(i) {
$('#productBtn' + i).show();
}
function onMouseOut(i) {
$('#productBtn' + i).hide();
}
I populate the item array with JSON. Is this necessary to include as well? Essentially they are set up like this:
var json = {
"items": [
{
"title": { "text": "#PART0001" },
"image": { "text": "../items/sale-october/PART0001.jpg" },
"description": { "text": "Item Description" },
"price": { "text": "$99.99" }
}
]
};
I'm especially confused, because I used this exact method for another section of my website (show/hiding a signup form), and that works just fine.
Soooo what am I doing wrong here? =/
Solved it:
Replaced the code under jQuery with the following:
/* Show Product Overlay */
function enlargeProduct() {
$('#productOverlay').toggle('fast', 'linear', 'closeProductOverlay()');
}
function closeProductOverlay() {
/* Hide Product Overlay */
$(document).on('click', function (event) {
if (!$(event.target).closest('#clickControl').length) {
$('#productOverlay').hide();
}
});
}
Seems that toggle is a way better way to do this. Still not entirely sure why the original didn't work, but I am happy! Hope this helps someone!

Javascript external function with event handler to generate DOM elements is not outputting html

I am trying to dynamically generate and remove input boxes based on selection. However, I have been tweaking this code for hours but can't figure out why it's not generating the html elements.
<!DOCTYPE html><html><head><title> Calculator</title>
<link rel="stylesheet" href="css/main.css." type="text/css"
<script src="js/addInputs.js"></script>
<script>
function addListeners() {
if (window.addEventListener) {
document.getElementById('sel_value').addEventListener("change", yourSelection, false);
}
function youSelection() {
// create three inputs with three labels fro array
if (document.getElementById('sel_value').value == 1) { addInputs(4,panel01);}
// create six inputs with six labels from array
else if (document.getElementById('sel_value').value == 2) { addInputs(6,panel02);}
else {
//clear panel 1
var remove_p01 = document.getElementById('panel01');
remove_p01.parentNode.removeChild(remove_p01);
// clear panel 2
var remove_p02 = document.getElementById('panel02');
remove_p02.parentNode.removeChild(remove_p02);
}
}
}
window.onload = addListeners;
</script></head><body>
// HTML Code
<div id="container">
<label for="addinputs">No. of Ports</label><!-- lablel for selector--->
<input id="sel_value" type="number" min="0" max="3" /><br />
</div></div></body></html>
// External Javascript File
function addInputs(num_of_inputs, div_id) {
"use strict";
var main_container, div, fieldLabel, input, count, label_array;
// Labels for input fields
label_array = ["Name", "Height", "Width", "Depth", "Position x", "Position Y"];
// main container id
main_container.document.getElementById('container');
// this div is to hold the input fields
div.document.createElement('div');
div.id = div_id;
// create labels and inputs
while (count < num_of_inputs) {
fieldLabel.createElement('input');
fieldLabel.type = "text";
fieldLabel.value = label_array[count];
fieldLabel.id = "r-port_label" + count;
input.document.createElement('input');
input.type = "number";
input.value = "0";
input.id = "r_port_input" + count;
// attach inputs and labels to parent div
div.appendChild(fieldLabel);
div.appendChild(input);
//increment input fields & labels
count += 1;
}
// attach parent div to page container
main_container.appendChild(div);
}
//CSS Code
#container{
width: 400px; min-height: 400px; background: #eeeeee;
}
I think you have a typo, should'nt this:
main_container.document.getElementById('container');
div.document.createElement('div');
fieldLabel.createElement('input');
input.document.createElement('input');
be:
main_container = document.getElementById('container');
div = document.createElement('div');
fieldLabel = document.createElement('input');
input = document.createElement('input');
Wow this look like a disaster. You have like 1000 errors, it looks like somebody delibretly scramble code. Anyhow there you go code that will do something, you adjust it how you wish.
You have:
wrong function calling in your listener
missing quotes when you passing ID of panels
not initialized counter
false element creation
luckily you have "user strict" :o)
function addInputs(num_of_inputs, div_id) {
"use strict";
var main_container, div, fieldLabel, input, count, label_array;
// Labels for input fields
label_array = ["Name", "Height", "Width", "Depth", "Position x", "Position Y"];
// main container id
main_container = document.getElementById('container');
// this div is to hold the input fields
div = document.createElement('div');
div.id = div_id;
// create labels and inputs
count = 0;
while (count < num_of_inputs) {
fieldLabel = document.createElement('input');
fieldLabel.type = "text";
fieldLabel.value = label_array[count];
fieldLabel.id = "r-port_label" + count;
input = document.createElement('input');
input.type = "number";
input.value = "0";
input.id = "r_port_input" + count;
// attach inputs and labels to parent div
div.appendChild(fieldLabel);
div.appendChild(input);
//increment input fields & labels
count += 1;
}
// attach parent div to page container
main_container.appendChild(div);
}
function addListeners() {
if (window.addEventListener) {
document.getElementById('sel_value').addEventListener("change",
function (){
// create three inputs with three labels fro array
if (document.getElementById('sel_value').value == 1) { addInputs(4,'panel01');}
// create six inputs with six labels from array
else if (document.getElementById('sel_value').value == 2) { addInputs(6,'panel02');}
else {
//clear panel 1
var remove_p01 = document.getElementById('panel01');
remove_p01.parentNode.removeChild(remove_p01);
// clear panel 2
var remove_p02 = document.getElementById('panel02');
remove_p02.parentNode.removeChild(remove_p02);
}
}
, false);
}
}
window.onload = addListeners;
#container{
width: 400px; min-height: 400px; background: #eeeeee;
}
<div id="container">
<label for="addinputs">No. of Ports</label><!-- lablel for selector--->
<input id="sel_value" type="number" min="0" max="3" /><br />
</div>
Hope it will help

Get value of element once then keep that value static

I am using
function expand(btn) {
var box = btn.parentNode.parentNode,
ipsum = box.getElementsByTagName("p")[0],
textSize = window.getComputedStyle(ipsum, null).getPropertyValue('font-size'),
lineHeight = window.getComputedStyle(ipsum, null).getPropertyValue('line-height'),
boxWidth = window.getComputedStyle(box, null).getPropertyValue('width'),
initialHeight = window.getComputedStyle(box, null).getPropertyValue('height'),
numText = parseInt(textSize),
numWidth = parseInt(boxWidth),
numHeight= parseInt(initialHeight);
if(box.style.height == "150px"){
box.style.height = "40px";
ipsum.style = "display:none";
}
else{
box.style.height = "150px";
ipsum.style = "display:inline";
}
console.log(lineHeight);
}
to get the initial height value of an element the only problem is that the element height changes frequently, but the first value obtained is always correct how can i get the initial value and keep it static?
how do i only store the value in the variable once, i need it in a variable to do calculations but as the value keeps changing i am getting the wrong number outputs.
You can refactor the function to store the initialHeight in a "private" variable the first time it's run:
var expand = (function() {
var initialHeight;
// Return a function that holds initialHeight in a closure
return function (btn) {
// Get box before setting/getting initialHeight
var box = btn.parentNode.parentNode;
// Set initialHeight only if undefined
initialHeight = initialHeight || window.getComputedStyle(box, null).getPropertyValue('height');
// Do other variables
var ipsum = box.getElementsByTagName("p")[0],
textSize = window.getComputedStyle(ipsum, null).getPropertyValue('font-size'),
lineHeight = window.getComputedStyle(ipsum, null).getPropertyValue('line-height'),
boxWidth = window.getComputedStyle(box, null).getPropertyValue('width'),
numText = parseInt(textSize),
numWidth = parseInt(boxWidth),
numHeight= parseInt(initialHeight);
if(box.style.height == "150px"){
box.style.height = "40px";
ipsum.style.display = "none";
} else {
box.style.height = "150px";
// If ipsum is a P, probably better to use "" (empty string) here
// so it returns to its default or inherited value
// ipsum.style.display = "inline";
ipsum.style.display = "";
}
console.log(lineHeight);
}
}());
The above is a proper refactoring, tested with the following markup:
<style type="text/css">
#box {border: 1px solid blue;}
#notBox {border: 1px solid red;}
#ipsum {border: 1px solid yellow;}
</style>
<div id="box">box
<div id="notBox">notBox
<input type="button" onclick="expand(this)" value="Expand…">
<p id="ipsum">ipsum</p>
</div>
</div>
Can you set it as an attribute? Something like:
// set the value once some place
box.setAttribute('data-init-height', window.getComputedStyle(…)… );
// when setting the initial height, check for the attribute first
initialHeight = box.getAttribute('data-init-height') || window.getComputedStyle(…)…;
Follow-up:
see fiddle
Each time you execute the function expand, you got a new value for initialHeight.
All that you need is to record it in a closure, with a hash if you're having more than 1 btn to handle, valued with arrays if you'd like to record multi heights for each btn. Just like this:
// predefine the function expand for furthre usage.
var expand;
(function() {
/*
having arrays as value, indexed like this:
{
<btn_1_id> : [<firstHeight>, <2nd Height>, ...],
<btn_2_id> : [],
...
}
*/
// Let's assume every btn is having an id. You may think another way yourself it they don't.
var initialHeightForMultiBtns = {};
expand = function(btn) {
// ...blablabla
initialHeightForMultiBtns[btn.id] = initialHeightForMultiBtns[btn.id] || [];
initialHeightForMultiBtns[btn.id].push(window.getComputedStyle(box, null).getPropertyValue('height'));
console.log(initialHeightForMultiBtns[btn.id][0]); // the real initialized height for the given btn.
// ...blablabla
}
})()
expand(btn_1); // let's expand btn_1 here.
Good luck.

Word wrap: ellipsis without css

I'm developing an app for a TV with an old Gecko engine (1.9 I think). I have a container 250x40 and I'd like to fit 2 lines of text in it, and if it's too long then an ellipsis should be shown (just like in the CSS3 property text-overflow: ellipsis).
However:
- I cannot use CSS3,
- I tried using some jQuery plugins, but they just work too slow - you can accually see the text being shrunk down until it fits in the container.
I tried counting letters, but it doesn't work, because they are all different widths.
I tried mapping each letter to its width, and counting the widht of the whole text, but the fact that it's 2 lines screws it all up - I don't know at which point the text will go to the next line.
Any help appreciated.
Slightly based on #Emissary's answer, here's a reasonably performing pair of jQuery plugins that'll handle adding ellipsis on elements that might hold more than one row of text:
(function($) {
$.fn.reflow = function() {
return this.each(function() {
var $this = $(this);
var $parent = $this.parent();
var text = $this.data('reflow');
$this.text(text); // try full text again
var words = text.split(' ');
while ($this.height() > $parent.height() ||
$this.width() > $parent.width()) {
words.pop();
$this.html(words.join(' ') + '…');
}
});
}
$.fn.overflow = function() {
return this.each(function() {
var $this = $(this);
var text = $this.text();
$this.data('reflow', text);
}).reflow();
}
})(jQuery);
The latter one registers elements for reflowing, and the first one actually does the work. The split is there to allow window resizing to automatically reformat the (original) contents of the element, e.g.:
$('.overflow').overflow();
$(window).on('resize', function() {
$('.overflow').reflow();
});
For higher performance, if it matters, you might consider replacing the .pop sequence with something that uses a O(log n) binary partitioning algorithm to find the optimal cut point instead of just removing one word at a time.
See http://jsfiddle.net/alnitak/vyth5/
It's been a while since I bothered with supporting older browsers but this is how I always did it. Should point out that it trims words rather than characters, I always thought half a word looked daft - if you care about typography...
html:
<div id="el">
<span class="overflow">Lorem ipsum dolor sit amet</span>
</div>
css:
#el {
width: 250px;
height: 40px;
overflow: hidden;
}
.overflow {
white-space: nowrap;
}
js / jQuery:
var el = $('#el'),
ov = $('#el .overflow'),
w = el.text().split(' ');
while(ov.width() > el.width()){
w.pop();
ov.html( w.join(' ') + '…' );
}
jsFiddle
This chap here has a solution that uses javascript and no JQuery: http://blog.mastykarz.nl/measuring-the-length-of-a-string-in-pixels-using-javascript/
Done in jsfiddle: http://jsfiddle.net/ZfDYG/
Edit - just read the bit about 2 lines of text: http://jsfiddle.net/ZfDYG/8/
code (for completeness):
String.prototype.visualLength = function() {
var ruler = $("ruler");
ruler.innerHTML = this;
return ruler.offsetWidth;
}
function $(id) {
return document.getElementById(id);
}
var s = "Some text that is quite long and probably too long to fit in the box";
var len = s.visualLength();
String.prototype.trimToPx = function(length,postfix) {
postfix = postfix || '';
var tmp = this;
var trimmed = this;
if (tmp.visualLength() > length) {
trimmed += postfix;
while (trimmed.visualLength() > length) {
tmp = tmp.substring(0, tmp.length-1);
trimmed = tmp + postfix;
}
}
return trimmed;
}
String.prototype.wrapToLength = function(complete) {
if(complete[this.length] == ' ' || complete[this.length - 1] == ' ') return;
var wrapped = this;
var found = false;
for(var i = this.length-1; i > 0 && !found; i--) {
if(this[i] == ' ') {
wrapped = this.substring(0, i);
found = true;
}
}
return wrapped;
}
var firstLine = s.trimToPx(240).wrapToLength(s);
var secondLine = s.substring(firstLine.length, s.length);
$('output').innerHTML= firstLine+' '+secondLine.trimToPx(240,'...');
Html:
<span id="ruler"></span>
<div id="output"></div>
css:
#ruler { visibility: hidden; white-space: nowrap; }
#output {
width: 240px;
height: 50px;
border: 1px solid red;
}
If that is still too slow on your box, I guess it should be possible to speed up the while loop by starting with bigger additions to oscillate towards the final length (kinda like a spring) rather than increment slowly up to it.

Categories