How to stop IE from redrawing while updating many elements - javascript

I have a page with many divs that are laid out in a grid. Each div has text in it. I want the text to be just big enough to fill the div. I do this by increasing the text size until I detect that the div has scrolled, and then go back, as per this answer:
Auto-size dynamic text to fill div
This works really well on most browsers, including mobile ones, but on IE10 it is very slow. You can actually watch it making the text smaller. I am guessing that it is doing some kind of window-wide layout operation each time the font size changes.
Any idea how to suspend the redraw until all of the divs are done or otherwise improve performance?
Here is a simple fiddle showing the technique. Just imagine this with about 50 divs on the page. Instant in Chrome, takes several seconds in IE10:
(function($) {
$.fn.textfill = function(options) {
var fontSize = options.maxFontPixels;
var ourText = $('span:visible:first', this);
var maxHeight = $(this).height();
var maxWidth = $(this).width();
var textHeight;
var textWidth;
do {
ourText.css('font-size', fontSize);
textHeight = ourText.height();
textWidth = ourText.width();
fontSize = fontSize - 1;
} while ((textHeight > maxHeight || textWidth > maxWidth) && fontSize > 3);
return this;
}
})(jQuery);
$(document).ready(function() {
$('.jtextfill').textfill({ maxFontPixels: 200 });
});
jsfiddle: http://jsfiddle.net/pRJdY/

well the jsFiddle fails cause there was a bug in the version of jQuery jsFiddle is referencing, http://bugs.jquery.com/ticket/13980.
So I created a local version of the sample. I think I see what you are talking about, but not totally sure. I see it go from the normal small text to much larger, but it happens real fast. I see the same behavior in Chrome. So my guess is till I have thousands of elements on the page I may not be able to recreate your issue.
I did optimize your JavaScript a little so I hope that helps you out a little:
(function ($) {
$.fn.textfill = function (options) {
var $this = $(this),
fontSize = options.maxFontPixels,
ourText = $('span:visible:first', this),
maxHeight = $this.height(),
maxWidth = $this.width(),
textHeight,
textWidth;
do {
ourText.css('font-size', fontSize);
textHeight = ourText.height();
textWidth = ourText.width();
fontSize = fontSize - 1;
} while ((textHeight > maxHeight || textWidth > maxWidth) && fontSize > 3);
return this;
}
})(jQuery);

The problem is the huge number of DOM accesses. Fortunately, your algorithm can be greatly optimized.
Starting at 200px fontsize and going down, when you're going to end up around 10-30px is very slow. Better to go up, and better to do a binary search for the correct size.
Starting at the minimum size and going up is 5x faster, in this case, at least: http://jsfiddle.net/pRJdY/2/
Binary search cuts the time required in half, again: http://jsfiddle.net/pRJdY/4/
(function($) {
$.fn.textfill = function(options) {
var start = (new Date).getTime(),
node = $(this),
max = options.maxFontPixels,
min = 3,
span = $('span:visible:first', this),
maxHeight = node.height(),
maxWidth = node.width(),
size,
isTooBig,
finalSize = null;
do {
console.log(size);
size = Math.round((min + max) / 2);
span.css('font-size', size);
isTooBig = (span.height() > maxHeight || span.width() > maxWidth);
if (isTooBig) {
max = size - 1;
} else {
min = size;
}
if (min === max) {
finalSize = max;
}
} while (finalSize === null);
span.css('font-size', finalSize);
console.log('elements:', node.size(), 'elapsed time:', (new Date).getTime() - start);
return this;
}
})(jQuery);

Related

Javascript animation with variable speed based on cursors position

What I want to achieve is a javascript animation with variable speed based on cursor position.
For that porpouse I'm using jquery's animate function and mousever event and javascript's setInterval function, but those aren't required, so if there is a better way to achieve it I would be more than happy to hear it (the only requeriment would be javascript).
The problem I'm facing is that I can't change speed dinamicly, for some reason the speed keeps adding to the one it already had instead of set what I wanted and even if it would change as spected it just doesn't happen in a smoothly way because of an unknown reason for me.
Here is the javascript that I have so far:
//settings for container_slider. Are used in startSlider() which handles the animation
var steps_animation_speed = 1000;
var steps_interval = 1500;
var steps_speed_factor = 1; // 100%
var amount_sliders = 3;
//cache DOM elements
var $container_slider = $('#container_slider');
var $shown_slides = $('.shown_slides', $container_slider);
var $slide = $(".slide");
// Just making sure sizing (widths) fits as they should.
var slides_width = $container_slider.width()/amount_sliders;
var slides_margin = parseInt($slide.css('marginLeft').replace('px', '')) + parseInt($slide.css('marginRight').replace('px', ''));
var steps_width = slides_width + slides_margin;
$shown_slides.css('width', steps_width*(amount_sliders+1) + 'px');
$slide.css('width', slides_width);
var interval;
// This function is responsible of the animation
function startSlider() {
$shown_slides.stop(false);
interval = setInterval(function() {
$shown_slides.animate({'margin-left': '-='+steps_width}, steps_animation_speed*steps_speed_factor, function() {
$('.shown_slides > li:last').after($('.shown_slides > li:first'));
$('.shown_slides').css('margin-left', '0');
});
}, steps_interval);
}
function pauseSlider() {
clearInterval(interval);
}
$container_slider.mouseleave(function(){
steps_interval = 3000;
$shown_slides.stop(true);
pauseSlider();
startSlider();
});
// $container_slider.mouseenter(function(){
// pauseSlider();
// });
$container_slider.mousemove(function(event){
pauseSlider();
var cursor_location = '';
if(event.pageX > 0 && event.pageX < 165){
cursor_location = "Cursor is on the left side";
// This is where i'm doing the tests that should work of changing animation's speed based on cursor position
if(steps_speed_factor !== (event.pageX / 165)){
steps_speed_factor = event.pageX / 165;
steps_speed_factor = (steps_speed_factor < 0.15 ? 0.15 : steps_speed_factor);
steps_interval = 0;
startSlider();
}
} else if(event.pageX > 165 && event.pageX < ($container_slider.width()-165)){
cursor_location = "Cursor is in the center (paused)";
// This stops animation, it could be achieved way better but i'm focusing on above's block of code.
steps_speed_factor = 1;
steps_interval = 3000;
$shown_slides.stop(true);
pauseSlider();
} else if(event.pageX > ($container_slider.width()-165) && event.pageX < $container_slider.width()) {
cursor_location = "Cursor is on the right side";
// This would be an exact copy (almost) of the left side, but since it doesn't work yet, this is pretty much a "blank" block of code
steps_interval = 0;
steps_speed_factor = ( event.pageX - ($container_slider.width() - 165) ) / 165;
}
$(".coordinates").html("X: " + event.pageX + " Y: " + event.pageY );
$(".cursor_location").html(cursor_location);
$(".speed_factor").html("Speed Factor is: "+steps_speed_factor);
});
startSlider();
Here is a codepen showing this javascript code "working".
--- EDIT
I forgot to explain propperly what happens in the codepen , since it is just an example didnt give it to much importance. Mainly what should happen is that the furthier the cursor is from the center, the tinier/faster the invervals of the animation should be without losing fluidness.
In this case i'm using a "speed factor" which I calculate by taking cursor's X position and then comparing it with a predefined area, converting it in a percentage (decimal) from 15% to 99%. But it isn't actually the important part. I'm clueless about how to achieve this and the more I try the messier my code gets, so as long as you can give me an example of changing animation's speed (in "real" time, i mean, smoothly/fluid) based on cursor's position as an example it would be perfect.

Autogrow textareas on copy paste in IE8

I am trying to write an autogrow functionality for text area on keyup . It works perfectly in firefox and for IE8 it works, but when try and copy paste a data having a lot of white space - it doesnot grow accordingly. It is surprising as am using scroll height not character count. Below is my code
$("textarea").keyup(function(){
if(navigator.userAgent.indexOf("Firefox") > 0){
$(this).css('height', 'auto' );
}
var elementHeight = 16;
$(this).css('overflow','scroll');
$(this).parent().css('overflow','scroll');
var height = $(this).prop('scrollHeight');
if(height >= elementHeight){
$(this).css('height',height+'px');
}
var parentHeight = height + 10;
if($(this).attr('id')=='evidenceDetails'){
parentHeight = parentHeight + 70;
}
$(this).parent().css('height',parentHeight+'px');
$(this).css('overflow','hidden');
$(this).parent().css('overflow','hidden');
});
Any help will highly appreciated.

Parallax stop background image pos change if scroll exceeds bg image size

I have multiple sections showing different backgrounds, each section has a basic parallax background image. As the backgrounds vary in height, I cannot seem to work out how to stop the background image position once the image bottom is reached.
Background position change begins if the section offset().top is equal to or greater than $(window).scrollTop().
It would seem that the btmOffset is incorrect but I can't see why.
Any help greatly appreciated.
Live example
http://demo.dwweb.co.uk
What I have so far
$window = $(window);
var winWid = $window.width();
$('.portfolioSection').each(function(){
var $bgobj = $(this);
var speed = 2.4;
var bg = $(this).css('background-image').replace('url("','').replace('")','');
var tmpImg = new Image();
tmpImg.src = bg;
var orgW = tmpImg.width;
var orgH = tmpImg.height;
var imgResizedRatio = winWid/orgW;
var resizedH = orgH * imgResizedRatio;
var btmOffset = (resizedH - $(this).height()) + $bgobj.offset().top;
$(window).scroll(function() {
if($(window).scrollTop() > $bgobj.offset().top && $(window).scrollTop() < btmOffset){
var yPos = -(($window.scrollTop()-$bgobj.offset().top) / speed);
var coords = '0 '+ yPos + 'px';
$bgobj.css({ backgroundPosition: coords });
} else if($(window).scrollTop() < $bgobj.offset().top) {
$bgobj.css({ backgroundPosition: '0 0' });
} else {
$bgobj.css({ backgroundPosition: '0 '+resizedH+'px' });
}
});
});
orgH and orgW will be 0 on execution, since you are creating a new Image asynchronously, while executing your code synchronously:
var tmpImg = new Image();
tmpImg.src = bg;
//...
This means, you would have to use the onload event (and maybe cover the onerror event too), like this:
tmpImg.onload = function(ev) {
var orgW = tmpImg.width;
var orgH = tmpImg.height;
//and the rest of your code...
}
This is very inefficient, since you are loading all these (big) images again.
I would add data-attributes to each .portfolioSection, like data-bgmaxscroll="1000" (which is the height of the image).
This would be a bit more hardcoded, but i think it's the easiest and the most performant way.

Checking a text element's width (based on font-size) against its parent container

I come to you with a tricky question:
Imagine you have the following basic structure:
<div><p>hello</p></div>
Now assume that div has display:block; and width:200px;.
Using javascript, how would you check what font-size gives you a 'hello' as big as possible without horizontal overflow (in the case of one word) or jumping to a 2nd line in case of a sentence or group of words?
I can't think of a way to measure the space occupied by text so that it can then be checked against that of the parent container, let alone checking if an element is overflowing or linejumping.
If there is a way, I'm sure this is the right place to ask.
Take a look at FitText
It is open source on github as well.
If you are interested in typography you might want to check out their other project called Lettering.js
There may be a method that's not as crazy, but this should be as precise as possible. Essentially, you have a div that you use to measure its width and incrementally increase the text content until it exceeds the width of the target div. Then, change the target div's <p>'s font size to the measuring div's minus 1:
http://jsfiddle.net/ExplosionPIlls/VUfAw/
var $measurer = $("<div>").css({
position: 'fixed',
top: '100%'
}).attr('id', 'measurer');
$measurer.append($("<p>").text($("p").text()));
$measurer.appendTo("body");
while ($measurer.width() <= $("#content").width()) {
$("#measurer p").css('font-size', '+=1px');
console.log($("#measurer").width());
}
$("#measurer p").css('font-size', '-=1px');
$("#content p").css('font-size', $("#measurer p").css('font-size'));
$measurer.remove();
Quick and dirty
fiddle
Set p's style to display: inline then run this
var dWidth = $("div").width();
var pWidth = $("p").width();
var starting = 1;
while (pWidth < dWidth) {
$("p").css("font-size",starting+"em");
pWidth = $("p").width();
starting = starting + .1;
}
Try this:
Auto-size dynamic text to fill fixed size container
(function($) {
$.fn.textfill = function(options) {
var fontSize = options.maxFontPixels;
var ourText = $('span:visible:first', this);
var maxHeight = $(this).height();
var maxWidth = $(this).width();
var textHeight;
var textWidth;
do {
ourText.css('font-size', fontSize);
textHeight = ourText.height();
textWidth = ourText.width();
fontSize = fontSize - 1;
} while ((textHeight > maxHeight || textWidth > maxWidth) && fontSize > 3);
return this;
}
})(jQuery);
$(document).ready(function() {
$('.jtextfill').textfill({ maxFontPixels: 36 });
});
<div class='jtextfill' style='width:100px;height:50px;'>
<span>My Text Here</span>
</div>
new jsFiddle Demo (updated 3/22/13)
I would just keep increasing the font size until the clientWidth or clientHeight changed. However, this becomes unreliable when using the actual element itself. To handle that situation, it is possible to create a span on the fly and then monitor the span's dimensions in order to properly retain the actual element's original sizes.
js
var adjuster = document.getElementById("adjust");
adjuster.onclick = function(){
var p = document.getElementById("p");
var text = p.innerText;
var s = document.createElement("span");
s.innerText = text;
p.innerHTML = "";
p.appendChild(s);
var h = p.clientHeight;
var w = p.clientWidth;
var size = 10;
while(true){
size++;
s.style.fontSize = size + "px";
if($(s).height() > h || $(s).width() > w){
size-=2;//rollback to no height change
s.style.fontSize = size + "px";
break;
}
}
p.style.fontSize = s.style.fontSize;
p.removeChild(s);
p.innerText = text;
};

jQuery - Fixing the animation

I am creating a new "whack-a-mole" style game where the children have to hit the correct numbers in accordance to the question.
I have the numbers animating from a set top position to another with a random width so that they look like they are floating up like bubbles.
The only problem I am having with it is that sometimes the numbers glitch and the width on them changes suddenly making it appear to jump from one side of the container to the other.
The only explanation I can think of is the width must be resetting somewhere which I have tried to look for.
Either I am blind or it is something else, can someone help me to find the source of the problem.
Here is the code that maps the numbers...
function randomFromTo(from, to) {
return Math.floor(Math.random() * (to - from + 1) + from);
}
function scramble() {
var children = $('#container').children();
var randomId = randomFromTo(1, children.length);
moveRandom("char" + randomId);
}
function moveRandom(id) {
var cPos = $('#container').offset();
var cHeight = $('#container').height();
var cWidth = $('#container').width();
var bWidth = $('#' + id).width();
var bHeight = $('#' + id).css(
'top', '400px'
).fadeIn(1000).animate({
' top': '-100px'
}, 10000).fadeOut(1000);
maxWidth = cPos.left + cWidth - bWidth;
minWidth = cPos.left;
newWidth = randomFromTo(minWidth, maxWidth);
$('#' + id).css({
left: newWidth
}).fadeIn(1000, function () {
setTimeout(function () {
$('#' + id).fadeOut(1000);
window.cont++;
}, 1000);
});
Here is also a working fiddle so you can see the issue I am talking about: http://jsfiddle.net/pUwKb/26/
The problem is that you are re-entering your moveRandom function for an ID that is already animated. The new width calculation causes the piece to seem to jump when it is reassigned during the already animated movement. One way to fix this is to reject new piece movements for pieces you are already animating. I modified your jsFiddle and fixed it with this code:
// Keep track of the pieces actually moving
var currentMoving = [];
function moveRandom(id) {
// If this one's already animating, skip it
if ($.inArray(id, currentMoving) !== -1) {
return;
}
// Mark this one as animating
currentMoving.push(id);
var cPos = $('#container').offset();
var cHeight = $('#container').height();
var cWidth = $('#container').width();
var bWidth = $('#' + id).width();
var bHeight = $('#' + id).css('top', '400px').fadeIn(1000).animate({
'top': '-100px'
}, 10000).fadeOut(1000);
maxWidth = cPos.left + cWidth - bWidth;
minWidth = cPos.left;
newWidth = randomFromTo(minWidth, maxWidth);
$('#' + id).css({
left: newWidth
}).fadeIn(1000, function () {
setTimeout(function () {
$('#' + id).fadeOut(1000);
// Mark this as no longer animating
var ix = $.inArray(id, currentMoving);
if (ix !== -1) {
currentMoving.splice(ix, 1);
}
window.cont++;
}, 1000);
});
}
Forked jsFiddle here.
Edit: The OP wanted to show more divs at once without speeding the animation up. To do this I added 20 more character divs (each a duplicate of the first 10 numbers), fixed the guarding code a bit, altered the CSS to specify the image of the character by class, and then put a limit of 20 animations at a given time. I also put a loop around the rejection of an already animated piece, to pick another. I made some other minor improvements. Updated JSFiddle here.

Categories