Trigger and reverse CSS transition with Javascript - javascript

I am currently exploring the possibilities of the FLIP technique which reduces all CSS transitions only to transform and opacity (because of GPU-acceleration). It involves manipulating styles directly with Javascript. Although it is not very hard to trigger such a transition, I've found myself unable to reverse it. Normally a transition defined within CSS on e.g. hover is reversed automatically when you stop hovering. But CSS is not enough to trigger (and reverse) transition on clicks. I want to be able to do the following:
a) Click on an item (and trigger size change by e.g. CSS class toggling)
b) Calculate the difference between its initial and new size and trigger a transform with transition
c) Click on it again while it is changing its size
d) Reverse the transition from the position it was in when clicked on
My problem is with the d) step. For some reason the element abruptly changes its size to neither the old nor the new size but to a completely different size. I have an example of what I'm trying to do here:
https://codesandbox.io/s/flamboyant-snowflake-47t59
Click on a square and then click on it again while it's enlarging.
Is there any reliable way to properly do what I'm trying to do? Are there good alternatives?

I've managed to find a working solution (it imports rematrix library to make calculations with transformation matrices easier). Both this solution and the simpler one by Richard are capable of 60fps animation - I guess the difference is the amount of cases each approach can deal with.
https://codesandbox.io/s/working-transform-reverse-urc16

I believe this is what you're looking for. Also, it seems to me that you don't quite understand requestAnimationFrame fully. It's made to replace a technique that involves recursive calls of setInterval to change the CSS property of an element every few milliseconds (read more here). So I suggest reading more about requestAnimationFrame first.
I'd like to note that this animation below can be far more easily made simply using CSS transition. Furthermore, MDN mentions that both have more or less similar performance anyway (read here). Still, here's a minimal working example of what you want:
let clicked = false;
let square = document.querySelector(".small-sq");
let expandID
let retractID
square.style.transformOrigin = 'top left'
square.style.transform = 'scale(1)'
square.addEventListener('click', e => {
let timeStart = new Date()
let currentScale = parseFloat(square.style.transform.slice(6, -1))
if (!clicked) {
cancelAnimationFrame(retractID)
expandID = requestAnimationFrame(() => repeat(timeStart, currentScale, clicked))
}
else {
cancelAnimationFrame(expandID)
retractID = requestAnimationFrame(() => repeat(timeStart, currentScale, clicked))
}
clicked = !clicked
})
function repeat(timeStart, currentScale, status) {
let timeDifference = new Date() - timeStart
if (status) {
let scaleValue = currentScale + (1 * timeDifference / 5000) > 2 ? 2 : currentScale + (1 * timeDifference / 5000)
square.style.transform = `scale(${scaleValue})`
expandID = requestAnimationFrame(() => repeat(timeStart, scaleValue, status))
if (scaleValue >= 2)
cancelAnimationFrame(expandID)
}
else {
let scaleValue = currentScale - (1 * timeDifference / 5000) < 1 ? 1 : currentScale - (1 * timeDifference / 5000)
square.style.transform = `scale(${scaleValue})`
retractID = requestAnimationFrame(() => repeat(timeStart, scaleValue, status))
if (scaleValue <= 1)
cancelAnimationFrame(retractID)
}
}
.small-sq {
height: 100px;
width: 100px;
background-color: rgb(131, 42, 131);
}
.big-sq {
height: 500px;
width: 500px;
background-color: rgb(131, 42, 131);
}
<div id="app">
<div class="small-sq"></div>
</div>
If you use CSS transition, then it can be converted to the following:
let square = document.querySelector('.small-sq')
square.addEventListener('click', e => {
square.classList.toggle('toggled')
})
.small-sq {
height: 100px;
width: 100px;
background-color: rgb(131, 42, 131);
transform-origin: left top;
transition: transform 1s linear;
}
.small-sq.toggled {
transform: scale(2)
}
<div id="app">
<div class="small-sq"></div>
</div>

Related

Is there a way to run a function if the user of my webpage hits a certain point on it?

I try do a webite with different divs or for me they are sections. If I reached the top of one of these it should console log this term. If u ask, ScrollHeight is equal to 1% of the devices' screenheight.
let Point1 = false;
document.addEventListener("scroll", e=> {
if (document.documentElement.scrollTop >= 150*ScrollHeight) {
if (Point1 == false){
Point1 = true;
Point1F();
};
}
})
function Point1F() {
console.log("U've done it');
}
But its not woking for me.
Your code works, as i think the problem why you don't see your .log() is because you didn't reach it.
If scrollHeight is (as you said) "1% of the devices' screenheight", then you need html height to be ~ 3x your screen height;
document.documentElement.style.height = "300vh";
// getting 1% of screen height
const scrollHeight = screen.height / 100;
const scrollTriggerPoint = scrollHeight * 150;
let point1 = false;
document.addEventListener("scroll", (e) => {
if (document.documentElement.scrollTop >= scrollTriggerPoint) {
if (point1 == false){
point1 = true;
point1F();
};
}
});
function point1F() {
console.log("u've done it");
}
P.S.
Don't use variable's/function's names starting with a capital letter, use it on;y for constructor functions or classes.
Intersection Observer API
Using scroll position is fine when you have a single trigger point. However, when there are multiple trigger points (as the question suggests) and they are not in a consistent position on different devices, then the Intersection Observer API is a useful solution.
MDN:
Implementing intersection detection in the past involved event
handlers and loops calling methods like
Element.getBoundingClientRect() to build up the needed information for
every element affected. Since all this code runs on the main thread,
even one of these can cause performance problems. When a site is
loaded with these tests, things can get downright ugly.
You create an observer on the document or a container element and then add the elements you want to watch. And the callback is triggered when an element reaches the threshold setting.
Demo Snippet
The snippet shows how to observe different sections as they scroll in and out of view.
// create an observer on the document or container element
let observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
// code to execute when the section becomes visible
console.log("is visible: " + entry.target.id);
// uncomment to trigger only once per section
// observer.unobserve(entry.target);
}
}, {
root: document, // or container element or null
rootMargin: "0px",
threshold: 0.1
});
// add each section to the observer
document.querySelectorAll("section").forEach(target => {
observer.observe(target);
});
section {
height: 5em;
margin: 1em;
margin-bottom: 20em;
background-color: lightblue;
}
Scroll down the page to trigger the observer
<section id="section1">Section 1</section>
<section id="section2">Sectopm 2</section>
<section id="section3">Section 3</section>
<section id="section4">Section 4</section>
<section id="section5">Section 5</section>

Any way to update the duration of an animation set via element.animate without restarting the animation?

I'm developing a game engine in HTML5. Characters are div elements using an animated sprite for background. As sprite animation have fluid parameters and must be set by code, they can't be predefined in a static CSS definition, thus I use element.animate to set sprite animations to a given row at a given speed knowing my scales and frame counts.
// Applies the given frame and animation to the sprite
// Frame is an angle, clockwise direction: 0 = up, 1 = right, 2 = down, 3 = left
set_animation(frame, duration) {
const scale_x = this.settings.sprite.scale_x * this.settings.sprite.frames_x;
const pos_y = this.settings.sprite.scale_y * -frame;
// Cancel the existing animation
if(this.data_actors_self.anim) {
this.data_actors_self.anim.cancel();
this.data_actors_self.anim = null;
}
// Play the animation for this row or show the first frame if static
if(duration > 0) {
this.data_actors_self.anim = this.element.animate([
{
backgroundPosition: px([0, pos_y])
}, {
backgroundPosition: px([scale_x, pos_y])
}
], {
duration: duration * 1000,
direction: "normal",
easing: "steps(" + this.settings.sprite.frames_x + ")",
iterations: Infinity
});
this.data_actors_self.anim.play();
} else {
this.element.style.backgroundPosition = px([0, pos_y]);
}
}
Obviously that's a snippet from an actor class function: this.element is the div, this.settings is an object with parameters to be used who's names should make sense in this context, the px() function is a simple converter to turn arrays into pixel strings for HTML (eg: [0, 0] to "0px 0px").
The issue I'm having: While I can always run this function to set a new animation, I want the ability to change the speed of the animation without resetting it. It doesn't need to be a smooth transition, for all I care the new speed can be applied at the next iteration... I only want to avoid a visual snap or any kind of reset upon applying the change. Once an animation is set, I have no idea how to access and update its duration parameter. Does anyone have any suggestions?
When using console.log on this.data.anim I'm rightfully told it's an animation object. I tried using JSON.stringify to get more information but nothing relevant is printed. this.data.anim.duration returns undefined so the setting must be stored under some other property. Even if I know that property, I'd like to be sure web browsers will agree with me changing it like this.data.anim.options.duration = new_duration.
You can wait for the end of an iteration before changing the animation duration if that is what is required.
This snippet only sets an event listener for animationiteration event when you click the button to increase the speed.
function upthespeed() {
const div = document.querySelector('div');
div.addEventListener('animationiteration', function() {
div.style.animationDuration = '1s';
});
document.querySelector('button').style.display = 'none';
}
div {
width: 10vmin;
height: 10vmin;
background-color: magenta;
animation: move 10s linear infinite;
}
#keyframes move {
0% {
transform: translateX(50vw);
}
50% {
transform: translateX(0);
}
100% {
transform: translateX(50vw);
}
}
<div></div>
<button onclick="upthespeed()">Click me to increase the speed at the end of the next iteration (you may have to wait!)</button>
The value for the animation duration isn't in the Animation object itself but in the CSS animation-duration property for the Element: so this.data_actors_self.style.animationDuration = new_duration will do the job. It will however restart the animation if it is being played, but if I understand correctly that isn't a problem for you.
Edit: To change the animation's duration without restarting it, all you have to do is set the value of anim.startTime to what it was before. For example:
const startTime = anim.startTime;
this.data_actors_self.style.animationDuration = new_duration
anim.startTime = startTime;

Is it possible to add a class to an element when positioned sticky to the top of the window? [duplicate]

I'm using the new position: sticky (info) to create an iOS-like list of content.
It's working well and far superior than the previous JavaScript alternative (example) however as far as I know no event is fired when it's triggered, which means I can't do anything when the bar hits the top of the page, unlike with the previous solution.
I'd like to add a class (e.g. stuck) when an element with position: sticky hits the top of the page. Is there a way to listen for this with JavaScript? Usage of jQuery is fine.
Demo with IntersectionObserver (use a trick):
// get the sticky element
const stickyElm = document.querySelector('header')
const observer = new IntersectionObserver(
([e]) => e.target.classList.toggle('isSticky', e.intersectionRatio < 1),
{threshold: [1]}
);
observer.observe(stickyElm)
body{ height: 200vh; font:20px Arial; }
section{
background: lightblue;
padding: 2em 1em;
}
header{
position: sticky;
top: -1px; /* ➜ the trick */
padding: 1em;
padding-top: calc(1em + 1px); /* ➜ compensate for the trick */
background: salmon;
transition: .1s;
}
/* styles for when the header is in sticky mode */
header.isSticky{
font-size: .8em;
opacity: .5;
}
<section>Space</section>
<header>Sticky Header</header>
The top value needs to be -1px or the element will never intersect with the top of the browser window (thus never triggering the intersection observer).
To counter this 1px of hidden content, an additional 1px of space should be added to either the border or the padding of the sticky element.
💡 Alternatively, if you wish to keep the CSS as is (top:0), then you can apply the "correction" at the intersection observer-level by adding the setting rootMargin: '-1px 0px 0px 0px' (as #mattrick showed in his answer)
Demo with old-fashioned scroll event listener:
auto-detecting first scrollable parent
Throttling the scroll event
Functional composition for concerns-separation
Event callback caching: scrollCallback (to be able to unbind if needed)
// get the sticky element
const stickyElm = document.querySelector('header');
// get the first parent element which is scrollable
const stickyElmScrollableParent = getScrollParent(stickyElm);
// save the original offsetTop. when this changes, it means stickiness has begun.
stickyElm._originalOffsetTop = stickyElm.offsetTop;
// compare previous scrollTop to current one
const detectStickiness = (elm, cb) => () => cb & cb(elm.offsetTop != elm._originalOffsetTop)
// Act if sticky or not
const onSticky = isSticky => {
console.clear()
console.log(isSticky)
stickyElm.classList.toggle('isSticky', isSticky)
}
// bind a scroll event listener on the scrollable parent (whatever it is)
// in this exmaple I am throttling the "scroll" event for performance reasons.
// I also use functional composition to diffrentiate between the detection function and
// the function which acts uppon the detected information (stickiness)
const scrollCallback = throttle(detectStickiness(stickyElm, onSticky), 100)
stickyElmScrollableParent.addEventListener('scroll', scrollCallback)
// OPTIONAL CODE BELOW ///////////////////
// find-first-scrollable-parent
// Credit: https://stackoverflow.com/a/42543908/104380
function getScrollParent(element, includeHidden) {
var style = getComputedStyle(element),
excludeStaticParent = style.position === "absolute",
overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/;
if (style.position !== "fixed")
for (var parent = element; (parent = parent.parentElement); ){
style = getComputedStyle(parent);
if (excludeStaticParent && style.position === "static")
continue;
if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX))
return parent;
}
return window
}
// Throttle
// Credit: https://jsfiddle.net/jonathansampson/m7G64
function throttle (callback, limit) {
var wait = false; // Initially, we're not waiting
return function () { // We return a throttled function
if (!wait) { // If we're not waiting
callback.call(); // Execute users function
wait = true; // Prevent future invocations
setTimeout(function () { // After a period of time
wait = false; // And allow future invocations
}, limit);
}
}
}
header{
position: sticky;
top: 0;
/* not important styles */
background: salmon;
padding: 1em;
transition: .1s;
}
header.isSticky{
/* styles for when the header is in sticky mode */
font-size: .8em;
opacity: .5;
}
/* not important styles*/
body{ height: 200vh; font:20px Arial; }
section{
background: lightblue;
padding: 2em 1em;
}
<section>Space</section>
<header>Sticky Header</header>
Here's a React component demo which uses the first technique
I found a solution somewhat similar to #vsync's answer, but it doesn't require the "hack" that you need to add to your stylesheets. You can simply change the boundaries of the IntersectionObserver to avoid needing to move the element itself outside of the viewport:
const observer = new IntersectionObserver(callback, {
rootMargin: '-1px 0px 0px 0px',
threshold: [1],
});
observer.observe(element);
If anyone gets here via Google one of their own engineers has a solution using IntersectionObserver, custom events, and sentinels:
https://developers.google.com/web/updates/2017/09/sticky-headers
Just use vanilla JS for it. You can use throttle function from lodash to prevent some performance issues as well.
const element = document.getElementById("element-id");
document.addEventListener(
"scroll",
_.throttle(e => {
element.classList.toggle(
"is-sticky",
element.offsetTop <= window.scrollY
);
}, 500)
);
After Chrome added position: sticky, it was found to be not ready enough and relegated to to --enable-experimental-webkit-features flag. Paul Irish said in February "feature is in a weird limbo state atm".
I was using the polyfill until it become too much of a headache. It works nicely when it does, but there are corner cases, like CORS problems, and it slows page loads by doing XHR requests for all your CSS links and reparsing them for the "position: sticky" declaration that the browser ignored.
Now I'm using ScrollToFixed, which I like better than StickyJS because it doesn't mess up my layout with a wrapper.
There is currently no native solution. See Targeting position:sticky elements that are currently in a 'stuck' state. However I have a CoffeeScript solution that works with both native position: sticky and with polyfills that implement the sticky behavior.
Add 'sticky' class to elements you want to be sticky:
.sticky {
position: -webkit-sticky;
position: -moz-sticky;
position: -ms-sticky;
position: -o-sticky;
position: sticky;
top: 0px;
z-index: 1;
}
CoffeeScript to monitor 'sticky' element positions and add the 'stuck' class when they are in the 'sticky' state:
$ -> new StickyMonitor
class StickyMonitor
SCROLL_ACTION_DELAY: 50
constructor: ->
$(window).scroll #scroll_handler if $('.sticky').length > 0
scroll_handler: =>
#scroll_timer ||= setTimeout(#scroll_handler_throttled, #SCROLL_ACTION_DELAY)
scroll_handler_throttled: =>
#scroll_timer = null
#toggle_stuck_state_for_sticky_elements()
toggle_stuck_state_for_sticky_elements: =>
$('.sticky').each ->
$(this).toggleClass('stuck', this.getBoundingClientRect().top - parseInt($(this).css('top')) <= 1)
NOTE: This code only works for vertical sticky position.
I came up with this solution that works like a charm and is pretty small. :)
No extra elements needed.
It does run on the window scroll event though which is a small downside.
apply_stickies()
window.addEventListener('scroll', function() {
apply_stickies()
})
function apply_stickies() {
var _$stickies = [].slice.call(document.querySelectorAll('.sticky'))
_$stickies.forEach(function(_$sticky) {
if (CSS.supports && CSS.supports('position', 'sticky')) {
apply_sticky_class(_$sticky)
}
})
}
function apply_sticky_class(_$sticky) {
var currentOffset = _$sticky.getBoundingClientRect().top
var stickyOffset = parseInt(getComputedStyle(_$sticky).top.replace('px', ''))
var isStuck = currentOffset <= stickyOffset
_$sticky.classList.toggle('js-is-sticky', isStuck)
}
Note: This solution doesn't take elements that have bottom stickiness into account. This only works for things like a sticky header. It can probably be adapted to take bottom stickiness into account though.
I know it has been some time since the question was asked, but I found a good solution to this. The plugin stickybits uses position: sticky where supported, and applies a class to the element when it is 'stuck'. I've used it recently with good results, and, at time of writing, it is active development (which is a plus for me) :)
I'm using this snippet in my theme to add .is-stuck class to .site-header when it is in a stuck position:
// noinspection JSUnusedLocalSymbols
(function (document, window, undefined) {
let windowScroll;
/**
*
* #param element {HTMLElement|Window|Document}
* #param event {string}
* #param listener {function}
* #returns {HTMLElement|Window|Document}
*/
function addListener(element, event, listener) {
if (element.addEventListener) {
element.addEventListener(event, listener);
} else {
// noinspection JSUnresolvedVariable
if (element.attachEvent) {
element.attachEvent('on' + event, listener);
} else {
console.log('Failed to attach event.');
}
}
return element;
}
/**
* Checks if the element is in a sticky position.
*
* #param element {HTMLElement}
* #returns {boolean}
*/
function isSticky(element) {
if ('sticky' !== getComputedStyle(element).position) {
return false;
}
return (1 >= (element.getBoundingClientRect().top - parseInt(getComputedStyle(element).top)));
}
/**
* Toggles is-stuck class if the element is in sticky position.
*
* #param element {HTMLElement}
* #returns {HTMLElement}
*/
function toggleSticky(element) {
if (isSticky(element)) {
element.classList.add('is-stuck');
} else {
element.classList.remove('is-stuck');
}
return element;
}
/**
* Toggles stuck state for sticky header.
*/
function toggleStickyHeader() {
toggleSticky(document.querySelector('.site-header'));
}
/**
* Listen to window scroll.
*/
addListener(window, 'scroll', function () {
clearTimeout(windowScroll);
windowScroll = setTimeout(toggleStickyHeader, 50);
});
/**
* Check if the header is not stuck already.
*/
toggleStickyHeader();
})(document, window);
#vsync 's excellent answer was almost what I needed, except I "uglify" my code via Grunt, and Grunt requires some older JavaScript code styles. Here is the adjusted script I used instead:
var stickyElm = document.getElementById('header');
var observer = new IntersectionObserver(function (_ref) {
var e = _ref[0];
return e.target.classList.toggle('isSticky', e.intersectionRatio < 1);
}, {
threshold: [1]
});
observer.observe( stickyElm );
The CSS from that answer is unchanged
Something like this also works for a fixed scroll height:
// select the header
const header = document.querySelector('header');
// add an event listener for scrolling
window.addEventListener('scroll', () => {
// add the 'stuck' class
if (window.scrollY >= 80) navbar.classList.add('stuck');
// remove the 'stuck' class
else navbar.classList.remove('stuck');
});

How to restart updated animation after dynamically changing keyframes?

I'm trying to create a marquee (yes, I've done LOTS of searching on that topic first) using animated text-indent. I prefer this solution over others I've tried, like using translation 100%, which causes text to leak out beyond the boundaries of my marquee.
I've been trying to follow this example here: https://www.jonathan-petitcolas.com/2013/05/06/simulate-marquee-tag-in-css-and-javascript.html
...which I've updated a bit, doing it in TypeScript, using API updates (appendRule instead of insertRule) and dropping concerns about old browser support.
The problem is that the animation restarts using the old keyframe rules -- the step described by the comment "re-assign the animation (to make it run)" doesn't work.
I've looked at what's going on in a debugger, and the rules are definitely being changed -- old rules deleted, new rules added. But it's as if the old rules are cached somewhere, and they aren't being cleared out.
Here's my current CSS:
#marquee {
position: fixed;
left: 0;
right: 170px;
bottom: 0;
background-color: midnightblue;
font-size: 14px;
padding: 2px 1em;
overflow: hidden;
white-space: nowrap;
animation: none;
}
#marquee:hover {
animation-play-state: paused;
}
#keyframes marquee-0 {
0% {
text-indent: 450px;
}
100% {
text-indent: -500px;
}
}
And the relevant section of my TypeScript:
function updateMarqueeAnimation() {
const marqueeRule = getKeyframesRule('marquee-0');
if (!marqueeRule)
return;
marquee.css('animation', 'unset');
const element = marquee[0];
const textWidth = getTextWidth(marquee.text(), element);
const padding = Number(window.getComputedStyle(element).getPropertyValue('padding-left').replace('px', '')) +
Number(window.getComputedStyle(element).getPropertyValue('padding-right').replace('px', ''));
const offsetWidth = element.offsetWidth;
if (textWidth + padding <= offsetWidth)
return;
marqueeRule.deleteRule('0%');
marqueeRule.deleteRule('100%');
marqueeRule.appendRule('0% { text-indent: ' + offsetWidth + 'px; }');
marqueeRule.appendRule('100% { text-indent: -' + textWidth + 'px; }');
setTimeout(() => marquee.css('animation', 'marquee-0 15s linear infinite'));
}
I've tried a number of tricks so far to get around this problem, including things like cloning the marquee element and replacing it with its own clone, and none of that has helped -- the animation continues to run as if the original stylesheet values are in effect, so the scrolling of the marquee doesn't adapt to different widths of text.
The next thing I'll probably try is dynamically creating new keyframes objects instead of editing the rules inside of an existing keyframes object, but that's a messy solution I'd rather avoid if anyone has a better solution.
I found a way to get my marquee working, and it did involved dynamically adding and removing keyframes rules from a stylesheet, but that wasn't as painful or ugly as I thought it might be.
let animationStyleSheet: CSSStyleSheet;
let keyframesIndex = 0;
let lastMarqueeText = '';
function updateMarqueeAnimation(event?: Event) {
const newText = marquee.text();
if (event === null && lastMarqueeText === newText)
return;
lastMarqueeText = newText;
marquee.css('animation', 'none');
const element = marquee[0];
const textWidth = getTextWidth(newText, element);
const padding = Number(window.getComputedStyle(element).getPropertyValue('padding-left').replace('px', '')) +
Number(window.getComputedStyle(element).getPropertyValue('padding-right').replace('px', ''));
const offsetWidth = element.offsetWidth;
if (textWidth + padding <= offsetWidth)
return;
if (!animationStyleSheet) {
$('head').append('<style id="marquee-animations" type="text/css"></style>');
animationStyleSheet = ($('#marquee-animations').get(0) as HTMLStyleElement).sheet as CSSStyleSheet;
}
if (animationStyleSheet.cssRules.length > 0)
animationStyleSheet.deleteRule(0);
const keyframesName = 'marquee-' + keyframesIndex++;
const keyframesRule = `#keyframes ${keyframesName} { 0% { text-indent: ${offsetWidth}px } 100% { text-indent: -${textWidth}px; } }`;
const seconds = (textWidth + offsetWidth) / 100;
animationStyleSheet.insertRule(keyframesRule, 0);
marquee.css('animation', `${keyframesName} ${seconds}s linear infinite`);
}
There's other stuff going on here not needed for a general solution. One thing is that this method is called for two reasons: The window is being resized, or an update to the marquee text has been made. I always want to update when the window is resized, but otherwise I don't want to update the animation if the text hasn't changed, otherwise it could unnecessarily reset when someone is trying to read it.
The other thing is that I don't want text to scroll at all if it happens to fit nicely without scrolling.

JavaScript animation

I am trying to animate a div moving 200px horizontally in JavaScript.
The code below makes it jump the pixels, but is there a way to make it look animated without using jQuery?
function () {
var div = document.getElementById('challengeOneImageJavascript');
div.style.left = "200px";
}
Here is a basic animation setup:
function animate(elem,style,unit,from,to,time) {
if( !elem) return;
var start = new Date().getTime(),
timer = setInterval(function() {
var step = Math.min(1,(new Date().getTime()-start)/time);
elem.style[style] = (from+step*(to-from))+unit;
if( step == 1) clearInterval(timer);
},25);
elem.style[style] = from+unit;
}
To use:
animate(
document.getElementById('challengeOneImageJavascript'),
"left","px",0,200,1000
);
This example will animate the given element to slide linearly from 0px to 200px over a time of 1 second (1000 ms).
You can easily do this through CSS3-Transition :
#challengeOneImageJavascript {
-webkit-transition: left .2s;
-moz-transition: left .2s;
-o-transition: left .2s;
transition: left .2s;
}
Though, it is not supported by IE9 and earlier browser versions.
I did a ton of research, and I finally learned how to do it really well.
I like to place my program in a window.onload function, that way it dosn't run the code until the page has finished loading.
To do the animation, make a function(I'll call it the draw function) and call it what ever you want except reserved words, then at the very end of the draw function call the requestAnimationFrame function and give it the name of the function to be called next frame.
Before the requestAnimationFrame function can be used it must be declared.
See the code below:
window.onload = function() {
function draw() { // declare animation function
context.fillStyle = "white";
context.fillRect(0, 0, 400, 400);
requestAnimationFrame(draw); // make another frame
}
var requestAnimationFrame = // declare the
window.requestAnimationFrame || // requestAnimationFrame
window.mozRequestAnimationFrame || // function
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
draw(); // call draw function
}
Note: Nothing after the line that calls the draw function will run, so you need to put everything you want to run before the line that calls the draw function.
You would have to use a javascript timeout function, and change the css value a little at a time. The easiest way would be to increment by a set amount each time until a threshold is reached, which would give you a linear animation, which would look clunky and amateurish compared to jQuery's default swing animation which follows a bezier curve approximately like an s-curve.
Untested code should do the linear animation
var lefty = 0;
var animate = function(){
lefty += 20;
var div = document.getElementById('challengeOneImageJavascript');
div.style.left = lefty +"px";
if(lefty < 200)
setTimeout(animate(),100);
}
animate()
n.b. there are lots of improvements to make to that block of code, but it should get you going...
With JavaScript, you will have to use setInterval function or this is how it can be done in jQuery:
$('#challengeOneImageJavascript').animate({left: '=-5'});
Adust value (5) as per your needs as well as direction via =- or =+
With Vanilla JavaScript:
var interval;
var animate = function(id, direction, value, end, speed){
var div = document.getElementById(id);
interval = setInterval(function() {
if (+(div.style) === end) {
clearInterval(interval);
return false;
}
div.style[direction] += value; // or -= as per your needs
}, speed);
}
And you can use it like:
animate('challengeOneImageJavascript', 'left', 5, 500, 200);
To stop animation any time, you would do:
clearInterval(interval);
Note: This just a very quick way to do it to give you an idea.
Simplest way via css.
https://jsfiddle.net/pablodarde/5hc6x3r4/
translate3d uses hardware acceleration running on GPU.
http://blog.teamtreehouse.com/increase-your-sites-performance-with-hardware-accelerated-css
HTML
<div class="movingBox"></div>
CSS
.movingBox {
width: 100px;
height: 40px;
background: #999;
transform: translate3d(0,0,0);
transition: all 0.5s;
}
.moving {
transform: translate3d(200px,0,0);
background: #f00;
}
JavaScript
const box = document.getElementsByClassName('movingBox')[0];
setTimeout(() => {
box.className += ' moving';
}, 1000);
CustomAnimation is a small libary for animating html elements which is written in pure js.You can use this libary.

Categories