CSS3 Keyframe injected with javascript does not work with Chrome - javascript

I am creating a keyframe with javascript because I want to know a specific element's width in order to apply an animation style using that.
Here is the code:
var animation = false,
animationstring = 'animation',
prefix = '',
domPrefixes = 'Webkit Moz O ms'.split(' '),
pfx = '',
elm = document.querySelector('.marquee');
// Checks if the animation implementation is unprefixed
if( elm.style.animationName ) { animation = true; }
// Apply correct prefixes if the animation implementation has prefixes
if( animation === false ) {
for( var i = 0; i < domPrefixes.length; i++ ) {
if( elm.style[ domPrefixes[i] + 'AnimationName' ] !== undefined ) {
pfx = domPrefixes[ i ];
animationstring = pfx + 'Animation';
prefix = '-' + pfx.toLowerCase() + '-';
animation = true;
break;
}
}
}
elm.style[ animationstring ] = 'marquee 20s linear infinite';
var keyframes = '#' + prefix + 'keyframes marquee { '+
'0% { ' + prefix + 'transform: translateX(100%);}'+
'100% { ' + prefix + 'transform: translateX(-' + elm.scrollWidth + 'px);}'+
'}';
document.styleSheets[0].insertRule( keyframes, 0 );
http://jsfiddle.net/69PXa/
My problem (I think) is on row 27, I apply elm.scrollWidth as the value for translateX. This apparently breaks the keyframes in Chrome while it works as it should in Firefox. If I instead just use a fixed number the animation works.
Is there a way to solve this?

You must apply the CSS to your .marquee after you define the actual keyframes. Right now you are telling the browser to animate using keyframes that don't exist yet.
JSFiddle

Maybe this: scrollWidth/scrollHeight give invalid dimensions
Or this: Is "ScrollWidth" property of a span not working on Chrome?
can help you ;-)
Regards.

Related

Text pagination inside a DIV with image

I want to paginate a text in some div so it will fit the allowed area
Logic is pretty simple:
1. split text into words
2. add word by word into and calculate element height
3. if we exceed the height - create next page
It works quite good
here is JS function i've used:
function paginate() {
var newPage = $('<pre class="text-page" />');
contentBox.empty().append(newPage);
var betterPageText='';
var pageNum = 0;
var isNewPage = false;
var lineHeight = parseInt(contentBox.css('line-height'), 10);
var wantedHeight = contentBox.height() - lineHeight;
for (var i = 0; i < words.length; i++) {
if (isNewPage) {
isNewPage = false;
} else {
betterPageText = betterPageText + ' ' + words[i];
}
newPage.text(betterPageText + ' ...');
if (newPage.height() >= wantedHeight) {
pageNum++;
if (pageNum > 0) {
betterPageText = betterPageText + ' ...';
}
newPage.text(betterPageText);
newPage.clone().insertBefore(newPage)
betterPageText = '...';
isNewPage = true;
} else {
newPage.text(betterPageText);
}
}
contentBox.craftyslide({ height: wantedHeight });
}
But when i add an image it break everything. In this case text overflows 'green' area.
Working fiddle: http://jsfiddle.net/74W4N/7/
Is there a better way to paginate the text and calculate element height?
Except the fact that there are many more variables to calculate,not just only the word width & height, but also new lines,margins paddings and how each browser outputs everything.
Then by adding an image (almost impossible if the image is higher or larger as the max width or height) if it's smaller it also has margins/paddings. and it could start at the end of a line and so break up everything again.basically only on the first page you could add an image simply by calculating it's width+margin and height+margin/lineheight. but that needs alot math to get the wanted result.
Said that i tried some time ago to write a similar script but stopped cause of to many problems and different browser results.
Now reading your question i came across something that i read some time ago:
-webkit-column-count
so i made a different approach of your function that leaves out all this calculations.
don't judge the code as i wrote it just now.(i tested on chrome, other browsers need different prefixes.)
var div=document.getElementsByTagName('div')[0].firstChild,
maxWidth=300,
maxHeigth=200,
div.style.width=maxWidth+'px';
currentHeight=div.offsetHeight;
columns=Math.ceil(currentHeight/maxHeigth);
div.style['-webkit-column-count']=columns;
div.style.width=(maxWidth*columns)+'px';
div.style['-webkit-transition']='all 700ms ease';
div.style['-webkit-column-gap']='0px';
//if you change the column-gap you need to
//add padding before calculating the normal div.
//also the line height should be an integer that
// is divisible of the max height
here is an Example
http://jsfiddle.net/HNF3d/10/
adding an image smaller than the max height & width in the first page would not mess up everything.
and it looks like it's supported by all modern browsers now.(with the correct prefixes)
In my experience, trying to calculate and reposition text in HTML is almost an exercise in futility. There are too many variations among browsers, operating systems, and font issues.
My suggestion would be to take advantage of the overflow CSS property. This, combined with using em sizing for heights, should allow you to define a div block that only shows a defined number of lines (regardless of the size and type of the font). Combine this with a bit of javascript to scroll the containing div element, and you have pagination.
I've hacked together a quick proof of concept in JSFiddle, which you can see here: http://jsfiddle.net/8CMzY/1/
It's missing a previous button and a way of showing the number of pages, but these should be very simple additions.
EDIT: I originally linked to the wrong version for the JSFiddle concept
Solved by using jQuery.clone() method and performing all calculations on hidden copy of original HTML element
function paginate() {
var section = $('.section');
var cloneSection = section.clone().insertAfter(section).css({ position: 'absolute', left: -9999, width: section.width(), zIndex: -999 });
cloneSection.css({ width: section.width() });
var descBox = cloneSection.find('.holder-description').css({ height: 'auto' });
var newPage = $('<pre class="text-page" />');
contentBox.empty();
descBox.empty();
var betterPageText = '';
var pageNum = 0;
var isNewPage = false;
var lineHeight = parseInt(contentBox.css('line-height'), 10);
var wantedHeight = contentBox.height() - lineHeight;
var oldText = '';
for (var i = 0; i < words.length; i++) {
if (isNewPage) {
isNewPage = false;
descBox.empty();
}
betterPageText = betterPageText + ' ' + words[i];
oldText = betterPageText;
descBox.text(betterPageText + ' ...');
if (descBox.height() >= wantedHeight) {
if (i != words.length - 1) {
pageNum++;
if (pageNum > 0) {
betterPageText = betterPageText + ' ...';
}
oldText += ' ... ';
}
newPage.text(oldText);
newPage.clone().appendTo(contentBox);
betterPageText = '... ';
isNewPage = true;
} else {
descBox.text(betterPageText);
if (i == words.length - 1) {
newPage.text(betterPageText).appendTo(contentBox);
}
}
}
if (pageNum > 0) {
contentBox.craftyslide({ height: wantedHeight });
}
cloneSection.remove();
}
live demo: http://jsfiddle.net/74W4N/19/
I actually came to an easier solution based on what #cocco has done, which also works in IE9.
For me it was important to keep the backward compatibility and the animation and so on was irrelevant so I stripped them down. You can see it here: http://jsfiddle.net/HNF3d/63/
heart of it is the fact that I dont limit height and present horizontal pagination as vertical.
var parentDiv = div = document.getElementsByTagName('div')[0];
var div = parentDiv.firstChild,
maxWidth = 300,
maxHeigth = 200,
t = function (e) {
div.style.webkitTransform = 'translate(0,-' + ((e.target.textContent * 1 - 1) * maxHeigth) + 'px)';
div.style["-ms-transform"] = 'translate(0,-' + ((e.target.textContent * 1 - 1) * maxHeigth) + 'px)';
};
div.style.width = maxWidth + 'px';
currentHeight = div.offsetHeight;
columns = Math.ceil(currentHeight / maxHeigth);
links = [];
while (columns--) {
links[columns] = '<span>' + (columns + 1) + '</span>';
}
var l = document.createElement('div');
l.innerHTML = links.join('');
l.onclick = t;
document.body.appendChild(l)

Append CSS class to head tag at runtime

In the following function I have been trying to append gradClass to apply gradient background to a div at runtime.
function applyGradient(upto) {
var gradStyle = "background: -webkit-linear-gradient(left, #ff1a00 20%,#ffffff 30%);"
+ "behavior: url(PIE-1.0.0/PIE.htc);"
+ "-pie-background: linear-gradient(#ff1a00 20%, #ffffff 30%);";
var newPercent = Math.floor(upto / end * 100);
gradStyle = gradStyle.replace(/20/gi, newPercent);
gradStyle = gradStyle.replace(/30/gi, "100");
gradClass = ".gradClass{" + gradStyle + "}";
//method 1
//jQuery('head').append($('<style>').text(gradClass));
//error: IE8 some known issue in jQuery library
//method 2
//var styleTag = document.createElement('style');
//styleTag.type = "text/css";
//styleTag.appendChild(document.createTextNode(gradClass));
//method 3
document.getElementsByTagName('style')[0].innerHTML += gradClass;
// Unknown runtime error in IE8.
jQuery("#container").addClass("gradClass");
}
Code works fine in Chrome but fails in IE8.
Having failed to apply the class using method 1 jQuery, I tried other two approaches as well.
What am I doing wrong ?
var $css = $('<style/>');
$css.appendTo('head');
$css.append("\n\
.hello { color:black; }\n\
.world { color:red; }");
You should try using JQuery's CSS function:
http://api.jquery.com/css/
$("div").click(function () {
var color = $(this).css("background-color");
$("#result").html("That div is <span style='color:" +
color + ";'>" + color + "</span>.");
});

Javascript fade in fade out without Jquery and CSS3

I am really squeezing my head to make the simple fade in and fade out of the background image work only with javascript without JQuery and CSS3. I know how easy is to call a fadeIn() and fadeOut() in Jquery. Unfortunately in my project I am working, they don't support Jquery. I want to support the animation from IE6 for your info.
On click of the links the corresponding background of the div to be faded in and out from the previously existing background. I am trying to make it work based on setinterval but could not do it.
function handleClick(evt){
var element = document.getElementsByClassName(evt.target.id);
fade(element);
}
function fade(element) {
var op = 1; // initial opacity
var timer = setInterval(function () {
if (op <= 0.1){
clearInterval(timer);
element.style.display = 'none';
}
element.style.opacity = op;
element.style.filter = 'alpha(opacity=' + op * 100 + ")";
op -= op * 0.1;
}, 50);
}
​
http://jsfiddle.net/meetravi/2Pd6e/4/
Here are my full implementations of fadeIn and fadeOut for cross-browser support (including IE6) which does not require jQuery or any other 3rd-party JS library:
function fadeIn( elem, ms )
{
if( ! elem )
return;
elem.style.opacity = 0;
elem.style.filter = "alpha(opacity=0)";
elem.style.display = "inline-block";
elem.style.visibility = "visible";
if( ms )
{
var opacity = 0;
var timer = setInterval( function() {
opacity += 50 / ms;
if( opacity >= 1 )
{
clearInterval(timer);
opacity = 1;
}
elem.style.opacity = opacity;
elem.style.filter = "alpha(opacity=" + opacity * 100 + ")";
}, 50 );
}
else
{
elem.style.opacity = 1;
elem.style.filter = "alpha(opacity=1)";
}
}
function fadeOut( elem, ms )
{
if( ! elem )
return;
if( ms )
{
var opacity = 1;
var timer = setInterval( function() {
opacity -= 50 / ms;
if( opacity <= 0 )
{
clearInterval(timer);
opacity = 0;
elem.style.display = "none";
elem.style.visibility = "hidden";
}
elem.style.opacity = opacity;
elem.style.filter = "alpha(opacity=" + opacity * 100 + ")";
}, 50 );
}
else
{
elem.style.opacity = 0;
elem.style.filter = "alpha(opacity=0)";
elem.style.display = "none";
elem.style.visibility = "hidden";
}
}
As others have said, you need to fix your handleClick to properly select a single element, then pass that element to the fade function (which I named fadeOut for clarity). The default time for a jQuery fade is 400ms, so if you want to mimic that, your call might look like this:
function handleClick( evt )
{
fadeOut( document.getElementById(evt.target.id), 400 );
}
getElementById givies you one element (or null), getElementsByClassName gives an array.
function handleClick(evt){
var element = document.getElementById(evt.target.id);
fade(element);
}
You seem to aim for usage of ID's, so this should answer your needs. I updated the whole thing: IDs
However, you should realize that this method of fading is much more costly than using GPU accelerated transitions.
Update
JSfiddle webkit opacity fade
If you do not care about IE7 - IE9, you can use very useful CSS3 transitions, something like this:
.element {
-webkit-transition: opacity 0.3s ease;
}
.element[faded=true] {
opacity: 0;
}
You will get very fast, native fade out effect without jQuery.
UPDATE:
Sorry, i hadn't read quiestion title thoroughly.
element.style is undefined because you're not referencing the correct object. Use element[0] for your function call:
function handleClick(evt){
var element = document.getElementsByClassName(evt.target.id);
fade(element[0]);
}
Fiddle
Side note: Using console.log() and some type of developer console (like the one included in Chrome) can work wonders for debugging.
You should really do this via CSS3 since all modern browsers support it, and for older browsers fallback to just using show/hide. Do this by adding a "fadeOut" class or removing it via JavaScript. CSS3 (transitions) handle everything else, including hiding and showing it for older browsers.
Remember: As much as possible, do things in CSS before doing them in JavaScript. Not only is it cleaner and easier to maintain but CSS3 animations render smoother as it often hardnesses the GPU (video card) and not just the CPU. This is especially important on mobile devices but is the standard, modern way for doing it in any device.
See this Opera article for greater detail:
http://dev.opera.com/articles/view/css3-show-and-hide/
I'll point you off in the right direction and leave the rest of the coding to you.
This is how the setInterval() function works. It takes a function to execute and then the milliseconds it should run for.
setInterval(function() {
if(fade(element[0]))
clearInterval();
}, 50);
I made a JS fiddle for you here It's semicomplete but shows off how you should go about making your fadeout/fadein.
This is tested in Chrome on a Mac. Not sure about FF nor IE unfortunately.
Also as several pointed out, when getting stuff by any function that ends with s you can be 100% sure that it gives you an array with elements and thus you have to refer to the element you want as such. In your case its element[0].
Hope I help you further a little ways! :) Good luck!
For a Single Function to toggle Fade IN or Out depending the case, here's my function
function toggleFade(elem, speed ) {
//Add Opacity Property if it doesnt exist
if (!elem.style.opacity) elem.style.opacity = 1;
if (elem.style.opacity <= 0) {
var inInterval = setInterval(function() {
elem.style.opacity = Number(elem.style.opacity)+0.02;
if (elem.style.opacity >= 1)
clearInterval(inInterval);
}, speed/50 );
}else{ // end if
var outInterval = setInterval(function() {
elem.style.opacity -= 0.02;
if (elem.style.opacity <= 0)
clearInterval(outInterval);
}, speed/50 );
}
}
I modified the function of #Raptor007
if (!Element.prototype.fadeIn) {
Element.prototype.fadeIn = function(){
let ms = !isNaN(arguments[0]) ? arguments[0] : 400,
func = typeof arguments[0] === 'function' ? arguments[0] : (
typeof arguments[1] === 'function' ? arguments[1] : null
);
this.style.opacity = 0;
this.style.filter = "alpha(opacity=0)";
this.style.display = "inline-block";
this.style.visibility = "visible";
let $this = this,
opacity = 0,
timer = setInterval(function() {
opacity += 50 / ms;
if( opacity >= 1 ) {
clearInterval(timer);
opacity = 1;
if (func) func('done!');
}
$this.style.opacity = opacity;
$this.style.filter = "alpha(opacity=" + opacity * 100 + ")";
}, 50 );
}
}
if (!Element.prototype.fadeOut) {
Element.prototype.fadeOut = function(){
let ms = !isNaN(arguments[0]) ? arguments[0] : 400,
func = typeof arguments[0] === 'function' ? arguments[0] : (
typeof arguments[1] === 'function' ? arguments[1] : null
);
let $this = this,
opacity = 1,
timer = setInterval( function() {
opacity -= 50 / ms;
if( opacity <= 0 ) {
clearInterval(timer);
opacity = 0;
$this.style.display = "none";
$this.style.visibility = "hidden";
if (func) func('done!');
}
$this.style.opacity = opacity;
$this.style.filter = "alpha(opacity=" + opacity * 100 + ")";
}, 50 );
}
}
How to use:
// fadeIn with default: 400ms
document.getElementById(evt.target.id).fadeIn();
// Calls the "alert" function with the message "done!" after 400ms - alert('done!');
document.getElementById(evt.target.id).fadeIn(alert);
// Calls the "alert" function with the message "done!" after 1500ms - alert('done!');
document.getElementById(evt.target.id).fadeIn(1500, alert);
JSfiddle fadeIn / fadeOut example

How jQuery do "fade" in IE8 and below?

I just wanted to know how jQuery can generate a fade effect in IE browsers when they don't support opacity? Animating opacity is the way they do the fade in other browsers like Firefox and Chrome.
I went into the code but honestly I couldn't find anything understandable to me!
From the jquery source, they basically detect if opacity is supported and if not, use IEs alpha filter
if ( !jQuery.support.opacity ) {
jQuery.cssHooks.opacity = {
get: function( elem, computed ) {
// IE uses filters for opacity
return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
( parseFloat( RegExp.$1 ) / 100 ) + "" :
computed ? "1" : "";
},
set: function( elem, value ) {
var style = elem.style,
currentStyle = elem.currentStyle;
// IE has trouble with opacity if it does not have layout
// Force it by setting the zoom level
style.zoom = 1;
// Set the alpha filter to set the opacity
var opacity = jQuery.isNaN( value ) ?
"" :
"alpha(opacity=" + value * 100 + ")",
filter = currentStyle && currentStyle.filter || style.filter || "";
style.filter = ralpha.test( filter ) ?
filter.replace( ralpha, opacity ) :
filter + " " + opacity;
}
};
}
using the following style filter:alpha(opacity=40)

Fade in on mouse movement

How do I fade in div content on first mouse movement, like on google.com, using JavaScript? I don't want it to fade out again.
Code: (See it in action)
// attach event handler
document.body.onmousemove = function(){
fadeIn( this, 1000 ); // 1000ms -> 1s
this.onmousemove = null; // remove to only fade in once!
};
// sets the opacity of an element (x-browser)
function setOpacity( obj, value ) {
if ( obj ) {
obj.style.opacity = value / 100;
obj.style.filter = 'alpha(opacity=' + value + ')';
obj.style.zoom = 1;
}
}
// makes an element to fade in
function fadeIn( dom, interval, delay ) {
interval = interval || 1000;
delay = delay || 10;
var opacity = 0,
start = Number(new Date()),
op_per_ms = 100 / interval;
if ( typeof dom === "string" ) {
dom = document.getElementById( dom );
}
function step() {
var now = Number(new Date()),
elapsed = now - start;
opacity = elapsed * op_per_ms;
setOpacity( dom, opacity );
if ( elapsed < interval )
setTimeout( step, delay );
else
setOpacity( dom, 100 );
}
setTimeout( step, delay );
};
Note: the fade function could've been smaller, but in this form you can reuse it easily for any element and duration. Have fun!
If you use jquery and want it to fade in like google you could do something like this
$('body').mousemove(function() {
$('#content').fadeIn();
});
You can create a fade in effect for the body just like Google with the following code. Please take into consideration this will have similar functionality to Google. You can apply this technique to any element with the proper event handler.
var fps = 24;
var mpf = 1000 / fps;
function fadeIn(ele, mils) {
// ele: id of document to change.
// mils: number of mils for the tansition.
var whole = 0;
var milsCount = 0;
var subRatio = 1 / (mils / mpf);
while (milsCount <= mils) {
setTimeout('setOpacity("' + ele + '", ' + whole + ')', milsCount);
whole += subRatio;
milsCount += mpf;
}
// removes the event handler.
document.getElementById(ele).onmouseover = "";
}
function setOpacity(ele, value) {
ele = document.getElementById(ele);
// Set both accepted values. They will ignore the one they do not need.
ele.style.opacity = value;
ele.style.filter = "alpha(opacity=" + (value * 100) + ")";
}
You will want to add the event handler to the body of the document in whatever fashion you normally do. Be sure to modify the fadeIn function to pull information from the target/srcElement if you decide to use an attachment method that does not accept arguments. Or you can hard code desired values and objects into the function:
Inline:
<body id="theBody" onmouseover="fadeIn('theBody', 1500)">
DOM Level 0:
document.getElementByTagName("body")[0].onmouseover = function(){ code here };
document.getElementByTagName("body")[0].onmouseover = fadeIn;
DOM Level 2:
document.getElementByTagName("body")[0].addEventListener("mouseover", fadeIn);
document.getElementByTagName("body")[0].attachEvent('onclick', fadeIn);
You will also want to set up a css rule for the body element to make sure that it is not visible when the page loads:
body {
opacity: 0;
filter:alpha(opacity=0);
}
I have checked this code to work correctly on IE8, Chrome, Safari, FireFox, and Opera. Good luck.
Using CSS3 animations, for whoever supports it at this point.
body {
opacity: 0;
-webkit-transition: opacity 0.3s linear;
}
In the above example, whenever we change the opacity on the body, it will do a fade effect lasting 0.3 seconds linearly. Attach it to mousemove for one time only.
document.body.onmousemove = function() {
this.style.opacity = 1;
this.onmousemove = null;
};
See google.com revamped here :) Chrome and Safari only.
I would recommend using a javascript library such as http://jquery.com/.
Using jquery, if you wanted to fade in a div with id "content", you could write the following javascript code...
$(document).mousemove(function() {
$("#content").fadeIn();
});

Categories