2

I used this jquery to move a div by every click.

$(document).ready(function(){
    $('#hero').click(function(){
        $(this).animate({
            left: '+=50px'
    },300);
    })
})

I'd like to avoid jquery whenever it is possible to get deeper into pure JS.

Is there anyway to achieve the same effect without using jquery?

I know that this will be more complex, but just trying to learn.

Rubens
  • 13,469
  • 10
  • 56
  • 90
xhallix
  • 2,519
  • 5
  • 31
  • 52

4 Answers4

1

you can use the same technique in javascript:

// get the object refrence
var hero_obj = document.getElementById('hero');

// attach the onclick event
hero_obj.onclick = function(){ 
    this.style.left = ( parseInt(this.style.left, 10) + 50 ) + 'px' 
};

However, the effect won't be as smooth as jquery

Felix Kling
  • 705,106
  • 160
  • 1,004
  • 1,072
SHANK
  • 2,780
  • 20
  • 31
  • You can you a timer to make it be an animation. This code just _move_ the object :) – Hieu Le Jan 19 '13 at 12:58
  • this does not work with my code :(
    – xhallix Jan 19 '13 at 13:06
  • @Christoph: You have to include the script at the right position. Before the ending `

    ` tag. If it is executed before the element exists, well, that cannot work. See also http://stackoverflow.com/questions/14028959/why-does-jquery-or-a-dom-method-such-as-getelementbyid-not-find-the-element.

    – Felix Kling Jan 19 '13 at 13:08
  • @Christoph: That only shows that JavaScript is executed there, but it still won't let you access the DOM element, because it does not exist yet at that moment. Please read the question I linked to and check your error console. The position might be "valid" but it is not "right". – Felix Kling Jan 19 '13 at 13:12
  • @FelixKling: at first, thank you for your great help. script is now before /body, which should give it access to the DOM, but still now action by click on the div – xhallix Jan 19 '13 at 13:16
  • @Christoph: Yeah, the other problem is that this solution won't work if `left` is not set on the element itself. That's because `this.style.left` would return an empty string and produce an invalid value. You'd have to get the value via `getComputedStyle` (google it) or set it on the element: http://jsfiddle.net/kcmZM/. – Felix Kling Jan 19 '13 at 13:18
  • great felix, thank you alot for jsfiddle. i will check it out later that day, and be sure I will get it to work, thank you! – xhallix Jan 19 '13 at 13:20
1

Here's a code with animation. This snippet is only for modern browsers, but it is easy to modify to work with older browsers (IEs) too. (Actually only attachment of the event needs to be fixed.)

window.onload = function () {
    var timer, k, intervals, kX, kY,
        counter = 0,
        hero = document.getElementById('hero'),
        posX = hero.offsetLeft,
        posY = hero.offsetTop,
        anim = function (elem, params) {
            posX += kX;
            posY += kY;
            elem.style.left = posX + 'px';
            elem.style.top = posY + 'px';
            if (counter > intervals) {
                clearInterval(timer);
                counter = 0;
            } else {
                counter++;
            }
            return;
        },
        move = function (elem, params) {
            if (timer) {
                clearInterval(timer);
                counter = 0;
            }
            k = Math.atan2(params.left, params.top);
            kX = Math.sin(k);
            kY = Math.cos(k);
            intervals = Math.floor(Math.sqrt(Math.pow(params.left, 2) + Math.pow(params.top, 2)));
            timer = setInterval(function () {
                anim(elem, params);
                return;
            }, params.speed);
            return;
        };
    document.getElementById('hero').addEventListener('click', function (e) {
        move(e.currentTarget, {left: 50, top: 0, speed: 0});
        return;
    }, false);
    return;
}

As you can see, with this code you can also move elements vertical and adjust speed. To switch direction, just add - to corresponding property. The code is using pixels only as units, but that's easy to modify if needed.

It's also easy to convert this functional code to an object. Also jQuery-like duration can be added by passing property params.duration instead of params.speed and doing some advanced calculations with that and kX, kY.

Working demo at jsFiddle

Teemu
  • 21,017
  • 6
  • 49
  • 91
1

I've stumbled upon this gem on vanilla-js.com a few weeks ago:

var s = document.getElementById('thing').style;
s.opacity = 1;
(function(){(s.opacity-=.1)<0?s.display="none":setTimeout(arguments.callee,40)})();

I really like the simplicity and the size of the code. Elegant and efficient!

I've created a function that affects the left property of an element of your choice based on the code above:

/* element: DOM element such as document.getElementById('hero')
   distance: distance in pixels to move to the left such as 50 or 100 */
function moveBy(element, distance){
    var target = isNaN(parseInt(s.left)) ? distance : parseInt(s.left) + distance;
    (function(){
        s.left = isNaN(parseInt(s.left)) ? '1px' : (parseInt(s.left) + 1).toString() + 'px';
        if(parseInt(s.left) <= distance) setTimeout(arguments.callee, 40);
    })();
}

You can play around and see what fits to your liking in terms of speed and smoothness. Try it here on a jsfiddle.

/* So you go: */
moveBy(document.getElementById('hero'), 50);
/* Or you can bind it to an event */
document.getElementById('hero').addEventListener('click', function(event){
    moveBy(this, 50);
});

What a solution like this would need if you're willing to make it better is to replace the left property by translate. As Paul Irish states on his blog, translate provides way better performance than moving elements around with TRBL (top-left-bottom-right). Some sort of easing functions could be added as well to smooth things out.

Rémi Breton
  • 4,030
  • 2
  • 20
  • 33
  • Nice, though [`arguments.callee`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments/callee) is probably going to be obsoleted in the future, and is forbidden in strict mode. +1 for maintioning `translate` as a better alternative. – Teemu Jan 19 '13 at 16:18
  • Yes you are correct, I didn't know. I found this: http://stackoverflow.com/questions/103598/why-was-the-arguments-callee-caller-property-deprecated-in-javascript – Rémi Breton Jan 19 '13 at 16:20
  • Well, that answer was very valid at the time it was posted (2008) ; ). – Teemu Jan 19 '13 at 16:23
  • Yeah, they write as if the fact that `arguments.callee` would be deprecated anytime soon and it was back in 2008. Probably not a good idea to suggest something like that then. – Rémi Breton Jan 19 '13 at 16:25
0

I needed to create an animation solution with easing a while back without using a framework.
The tricky part for me was coping with interrupting/restarting animations part way through when they are tied to user interactions. I found that you can run into trouble pretty quickly if your animations double-fire.

Here is on github: https://github.com/robCrawford/js-anim

There are a few supporting functions but here's the main animation:

function animate(el, prop, to, pxPerSecond, easing, callback){
/**
* Animate style property
* i.e. animate(div1, "width", 1100, 1000, "out", function(){console.log('div1 anim end')});
* 
* @param el  DOM element
* @param prop  Property to animate
* @param to  Destination property value
* @param pxPerSecond  Speed of animation in pixels per second
* @param easing (optional)  Easing type: "in" or "out"
* @param callback (optional)  Function to call when animation is complete
*/
    var frameDur = 10,
        initPropVal = parseInt(getCurrCss(el, prop)),
        distance = Math.abs(to-initPropVal),
        easeVal = (easing==="in")?1.5:(easing==="out")?0.5:1, // >1 ease-in, <1 ease-out
        elAnimData = getData(el, 'animData');

    //Quit if already at 'to' val (still fire callback)
    if(initPropVal===to){
        if(callback)callback.call();
        return;
    }

    //Init animData for el if first anim
    if(!elAnimData){
        elAnimData = {};
        setData(el, {'animData':elAnimData});
    }

    //Get data for prop being animated or create entry
    var animDataOb = elAnimData[prop];
    if(!animDataOb)animDataOb = elAnimData[prop] = {};

    //Don't re-initialise an existing animation i.e. same prop/to
    if(animDataOb.to === to)return;
    animDataOb.to = to; //Store 'to' val

    //Clear any exisiting interval
    if(animDataOb.intId){
        clearInterval(animDataOb.intId);
        animDataOb.intId = null;
    }

    //Create new anim
    animDataOb.intId = (function(animDataOb){
        var totalSteps = Math.round((distance/pxPerSecond)/(frameDur*.001)),
            thisStep = 0;

        return setInterval(function(){
            var newVal = easeInOut(initPropVal, to, totalSteps, thisStep++, easeVal);
            if(!isNaN(newVal))el.style[prop] = newVal + "px"; //Allow 0
            if(thisStep > totalSteps)endAnim(animDataOb, callback);
        }, frameDur);
    })(animDataOb);
}

function endAnim(animDataOb, callback){
//End anim
    clearInterval(animDataOb.intId);
    animDataOb.intId = animDataOb.to = null;
    if(callback)callback.call();
}
robC
  • 2,342
  • 1
  • 17
  • 27