Interactive Physics Animations Javascript Canvas 17

    I’m really enjoying giving a control panel of sorts to the end user and letting them control the physics rules of their canvas. Let’s add more motion to it by applying a jitter to the velocity of each dot. This is similar to earlier, when we first started animating by applying a random number to the coordinates, but now we’ll apply a (smaller) random number to the velocity. This will be much smoother and will give a sense of real life to the dots. Straight lines and trajectories is always theoretical and hypothetical, but to make things look more real, sometimes we have to break the clean, straight lines. This will be another checkbox to control the float. I’m really enjoying the fact that the more properties we add to our controls we get exponentially more possible configurations. Perhaps we should even give a slider to control the strength of gravity…
    interactive physics animations via javascript & canvas | 17.

    $(function () {
        var canvas, context, width, height, x, y, radius = 25, clickX, clickY, drag = false;
        var total_dots = 10;
        var fps = 24;
       
        canvas = $("#canvas")[0];
        context = canvas.getContext("2d");
        var dots = new Array();
        var drag_i = -1;
        var gravity = 0;
        var friction = .98;
        var bounce = -.96;
        var wrap = false;
        var float = true;
       
        var this_dot = {};
        for (var i=0; i < total_dots; i++){
            createDot();
        }
        function createDot(x, y, r, vx, vy){
            var this_dot = {
                x:      typeof(x) != 'undefined' ? x : Math.random()*canvas.width,
                y:      typeof(y) != 'undefined' ? y : Math.random()*canvas.height,
                radius: typeof(r) != 'undefined' ? r : Math.random()*20+10,
                vx:     typeof(vx) != 'undefined' ? vx : Math.random()*30-10,
                vy:     typeof(vy) != 'undefined' ? vy : Math.random()*30-10
            };
            dots.push(this_dot);
        }
       
        draw();
       
        $("#canvas").mousedown(function (event) {
            var dx, dy, dist;
            for (var i=0; i < dots.length; i++){
                dx = event.pageX - this.offsetLeft - dots[i].x;
                dy = event.pageY - this.offsetTop - dots[i].y;
                dist = Math.sqrt(dx * dx + dy * dy);
                if(dist < radius) {
                    drag = true;
                    drag_i = i
                    clickX = dx;
                    clickY = dy;
                    continue;
                }
            }
            //none clicked
            if (!drag) {
                createDot(event.pageX - this.offsetLeft, event.pageY - this.offsetTop);
            }
        });
     
        $("#canvas").mouseup(function (event) {
            drag = false;
            drag_i = -1;
        });
     
        $("#canvas").mousemove(function (event) {
            if(drag) {
                dots[drag_i].old_x = dots[drag_i].x;
                dots[drag_i].old_y = dots[drag_i].y;
                dots[drag_i].x = event.pageX - this.offsetLeft - clickX;
                dots[drag_i].y = event.pageY - this.offsetTop - clickY;
                dots[drag_i].vx = dots[drag_i].x - dots[drag_i].old_x;
                dots[drag_i].vy = dots[drag_i].y - dots[drag_i].old_y;
                draw();
            }
        });
        function update(){
            for (var i=0; i < dots.length; i++){
                if (drag_i != i){
                    var this_dot = dots[i];
                    if (float){
                        this_dot.vx += Math.random() - .5;
                        this_dot.vy += Math.random() - .5;
                    }
                    this_dot.vx *= friction;
                    this_dot.vy = this_dot.vy * friction + gravity;
                    this_dot.x += this_dot.vx;
                    this_dot.y += this_dot.vy;
                   
                    if (wrap){
                        if (this_dot.x > canvas.width + this_dot.radius){
                                this_dot.x -= canvas.width + this_dot.radius*2;
                        }
                        else if(this_dot.x < 0 - this_dot.radius){
                                this_dot.x += canvas.width + this_dot.radius*2;
                        }
                        if (this_dot.y > canvas.height + this_dot.radius){
                                this_dot.y -= canvas.height + this_dot.radius*2;
                        }
                        else if(this_dot.y < 0 - this_dot.radius){
                                this_dot.y += canvas.height + this_dot.radius*2;
                        }
                    }
                    else if (!wrap) {
                        if (this_dot.x > canvas.width - this_dot.radius){
                            this_dot.x = canvas.width - this_dot.radius;
                            this_dot.vx = this_dot.vx * bounce;
                        }
                        else if(this_dot.x < 0 + this_dot.radius){
                            this_dot.x = this_dot.radius;
                            this_dot.vx = this_dot.vx * bounce;
                        }
                        if (this_dot.y > canvas.height - this_dot.radius){
                            this_dot.y = canvas.height - this_dot.radius;
                            this_dot.vy = this_dot.vy * bounce;
                        }
                        else if(this_dot.y < 0 + this_dot.radius){
                            this_dot.y = this_dot.radius;
                            this_dot.vy = this_dot.vy * bounce;
                        }
                    }
                }
            }
        }
        function draw() {
            context.clearRect(0, 0, canvas.width, canvas.height);
            for (var i=0; i < dots.length; i++){
                context.beginPath();
                context.arc(dots[i].x, dots[i].y, dots[i].radius, 0, Math.PI * 2, false);
                context.fill();
                context.closePath();
            }
        }
       
        setInterval(function() {
            update();
            draw();
        }, 1000/fps);  

        $("#gravity").click(function(){
            if($("#gravity").is(':checked')){
                gravity = 2;   
            }
            else{
                gravity = 0;
            }
        });
        $("#wrap").click(function(){
            if($("#wrap").is(':checked')){
                wrap = true;   
            }
            else{
                wrap = false;
            }
        });
        $("#float").click(function(){
            if($("#float").is(':checked')){
                float = true;  
            }
            else{
                float = false;
            }
        });
       
    });

    Follow the whole Interactive Physics Animations via Javascript & Canvas series.

    This entry was posted in interactive javascript canvas, tutorial and tagged , , , , , , , , , , , , , , , , , , , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

    6 Comments

    1. paradox
      Posted November 22, 2011 at 11:08 am | Permalink

      nice…

    2. Thomas
      Posted March 8, 2012 at 7:59 am | Permalink

      Nice Javascript Animation Series! Makes fun to play with!
      But what if i wanna create a timebased animation loop?

      I’ve started with adding:

      setInterval(function() {
                  time += 100;
                  if(time == 16000) time = 0;
              });

      And building a if(time >= 4000) structure within the update()-function
      to create different timebased animations… but this isnt as easy as i thought.

      Got some ideas?

      complete code:

      $(function () {
              var canvas, canvas_w, canvas_h, context, width, height, x, y = false, radius = 10;
              var total_dots = 10;
              var fps = 24;
             
              canvas = $("#canvas")[0];
              canvas.width = $(window).width();
              canvas.height = $(window).height();
              canvas_w = $("#canvas").width();
              canvas_h = $("#canvas").height();
              context = canvas.getContext("2d");
             
              var dots = new Array();
              var drag_i = -1;
              var gravity = 0;
              var friction = .98;
              var bounce = -.96;
              var wrap = false;
              var time = 0;
             
             
              var circColor = [];
              circColor[0] = '#260053';
              circColor[1] = '#40001b';
              circColor[2] = '#000057';
              circColor[3] = '#000000';
             
              var this_dot = {};

              function createDot(x, y, r, vx, vy){
                 
                  for (var j = 0; j < circColor.length; j++) {
                      for (var i = 0; i < total_dots; i++) {
                          var this_dot = {
                              x: typeof(x) != 'undefined' ? x : Math.random() * canvas.width,
                              y: typeof(y) != 'undefined' ? y : Math.random() * canvas.height,
                              radius: typeof(r) != 'undefined' ? r : (i / total_dots) * Math.random() * 10,
                              vx: typeof(vx) != 'undefined' ? vx : Math.random() * 30 - 10,
                              vy: typeof(vy) != 'undefined' ? vy : Math.random() * 30 - 10,
                              color: typeof(color) != 'undefined' ? color : circColor[j],
                              speed: typeof(speed) != 'undefined' ? speed : 5
                          };
                          dots.push(this_dot);
                      }
                  }
              }
              createDot();
             
              draw();
             
              function update(){
                 
                  for (var i=0; i < dots.length; i++){
                      var this_dot = dots[i];
                     
                      console.log(time);
                      if (time >= 0) {
                          this_dot.vx += this_dot.speed * 3;
                          this_dot.vy += this_dot.speed * 3;
                          wrap = true;
                          this_dot.vy = this_dot.vy + gravity;
                          this_dot.x += this_dot.vx / this_dot.radius / 300;
                          this_dot.y += this_dot.vy / this_dot.radius / 300;
                      }
                     
                      if (time >= 4000) {
                         
                          this_dot.vx += this_dot.speed * 3;
                          this_dot.vy += this_dot.speed * 3;
                          wrap = true;
                          this_dot.vy = this_dot.vy + gravity;
                          this_dot.x += this_dot.vx / this_dot.radius / 100;
                          this_dot.y += this_dot.vy / this_dot.radius / 100;
                      }
                      if (time >= 8000) {
                      }
                     
                      //if wrap dots are bounce back
                      if (wrap){
                          if (this_dot.x > canvas.width + this_dot.radius){
                                  this_dot.x -= canvas.width + this_dot.radius*2;
                          }
                          else if(this_dot.x < 0 - this_dot.radius){
                                  this_dot.x += canvas.width + this_dot.radius*2;
                          }
                          if (this_dot.y > canvas.height + this_dot.radius){
                                  this_dot.y -= canvas.height + this_dot.radius*2;
                          }
                          else if(this_dot.y < 0 - this_dot.radius){
                                  this_dot.y += canvas.height + this_dot.radius*2;
                          }
                      }
                      else if (!wrap) {
                          if (this_dot.x > canvas.width - this_dot.radius){
                              this_dot.x = -canvas.width - this_dot.radius;
                              this_dot.vx = this_dot.vx * bounce;
                          }
                          else if(this_dot.x < 0 + this_dot.radius){
                              this_dot.x = this_dot.radius;
                              this_dot.vx = this_dot.vx * bounce;
                          }
                          if (this_dot.y > canvas.height - this_dot.radius){
                              this_dot.y = +canvas.height - this_dot.radius;
                              this_dot.vy = this_dot.vy * bounce;
                          }
                          else if(this_dot.y < 0 + this_dot.radius){
                              this_dot.y = this_dot.radius;
                              this_dot.vy = this_dot.vy * bounce;
                          }
                      }
                  }
              }
             
              function draw() {
                  //clean frame
                  context.clearRect(0, 0, canvas.width, canvas.height);
                 
                  //update all dots
                  for (var i=0; i < dots.length; i++){
                      context.beginPath();
                      context.arc(dots[i].x, dots[i].y, dots[i].radius, 0, Math.PI * 2, false);
                      context.fillStyle = dots[i].color;
                      context.fill();
                      context.closePath();
                  }
              }
             
              setInterval(function() {
                  time += 100;
                  if(time == 16000) time = 0;
              });
             
              setInterval(function() {
                  update();
                  draw();
              }, 1000/fps);
             
              $(window).resize(function(){
                  canvas.width = $(window).width();
                  canvas.height = $(window).height();
              });
             
          });
      • Posted March 9, 2012 at 9:36 am | Permalink

        @Thomas -

        Hey, that’s a great idea. Taking a look at your code I think you’re 90% of the way there. It seems like your timing logic is a little off. I made a couple modifications and see it working great: http://jsfiddle.net/circlecube/zWE9L/5/embedded/result/

        Most notable is your if (time == x) statements, it seems you’d want those to be

        if(), if else() and else()

        , rather than all if, no? Also, the evaluations do not look right, i think you’d want to check that the time was between a certain range rather than just greater than one value:

        if (time >= 0 && time < 4000) {

        Hope that helps! Be sure and share what you end up with!

        $(function () {
                var canvas, canvas_w, canvas_h, context, width, height, x, y = false, radius = 10;
                var total_dots = 10;
                var fps = 24;
               
                canvas = $("#canvas")[0];
                canvas.width = $(window).width();
                canvas.height = $(window).height();
                canvas_w = $("#canvas").width();
                canvas_h = $("#canvas").height();
                context = canvas.getContext("2d");
               
                var dots = new Array();
                var drag_i = -1;
                var gravity = 0;
                var friction = .98;
                var bounce = -.96;
                var wrap = false;
                var time = 0;
               
               
                var circColor = [];
                circColor[0] = '#260053';
                circColor[1] = '#40001b';
                circColor[2] = '#000057';
                circColor[3] = '#000000';
               
                var this_dot = {};

                function createDot(x, y, r, vx, vy){
                   
                    for (var j = 0; j < circColor.length; j++) {
                        for (var i = 0; i < total_dots; i++) {
                            var this_dot = {
                                x: typeof(x) != 'undefined' ? x : Math.random() * canvas.width,
                                y: typeof(y) != 'undefined' ? y : Math.random() * canvas.height,
                                radius: typeof(r) != 'undefined' ? r : (i / total_dots) * Math.random() * 10,
                                vx: typeof(vx) != 'undefined' ? vx : Math.random() * 30 - 10,
                                vy: typeof(vy) != 'undefined' ? vy : Math.random() * 30 - 10,
                                color: typeof(color) != 'undefined' ? color : circColor[j],
                                speed: typeof(speed) != 'undefined' ? speed : 5
                            };
                            dots.push(this_dot);
                        }
                    }
                }
                createDot();
               
                draw();
               
                function update(){
                   
                    for (var i=0; i < dots.length; i++){
                        var this_dot = dots[i];
                       
                        console.log(time);
                        if (time >= 0 && time < 4000) {
                            this_dot.vx += this_dot.speed * 3;
                            this_dot.vy += this_dot.speed * 3;
                            wrap = true;
                            this_dot.vy = this_dot.vy + gravity;
                            this_dot.x += this_dot.vx / this_dot.radius / 300;
                            this_dot.y += this_dot.vy / this_dot.radius / 300;
                        }
                       
                        else if (time >= 4000 && time < 8000) {
                           
                            this_dot.vx += this_dot.speed * 3;
                            this_dot.vy += this_dot.speed * 3;
                            wrap = true;
                            this_dot.vy = this_dot.vy + gravity;
                            this_dot.x += this_dot.vx / this_dot.radius / 100;
                            this_dot.y += this_dot.vy / this_dot.radius / 100;
                        }
                        else if (time >= 8000) {
                        }
                       
                        //if wrap dots are bounce back
                        if (wrap){
                            if (this_dot.x > canvas.width + this_dot.radius){
                                    this_dot.x -= canvas.width + this_dot.radius*2;
                            }
                            else if(this_dot.x < 0 - this_dot.radius){
                                    this_dot.x += canvas.width + this_dot.radius*2;
                            }
                            if (this_dot.y > canvas.height + this_dot.radius){
                                    this_dot.y -= canvas.height + this_dot.radius*2;
                            }
                            else if(this_dot.y < 0 - this_dot.radius){
                                    this_dot.y += canvas.height + this_dot.radius*2;
                            }
                        }
                        else if (!wrap) {
                            if (this_dot.x > canvas.width - this_dot.radius){
                                this_dot.x = -canvas.width - this_dot.radius;
                                this_dot.vx = this_dot.vx * bounce;
                            }
                            else if(this_dot.x < 0 + this_dot.radius){
                                this_dot.x = this_dot.radius;
                                this_dot.vx = this_dot.vx * bounce;
                            }
                            if (this_dot.y > canvas.height - this_dot.radius){
                                this_dot.y = +canvas.height - this_dot.radius;
                                this_dot.vy = this_dot.vy * bounce;
                            }
                            else if(this_dot.y < 0 + this_dot.radius){
                                this_dot.y = this_dot.radius;
                                this_dot.vy = this_dot.vy * bounce;
                            }
                        }
                    }
                }
               
                function draw() {
                    //clean frame
                    context.clearRect(0, 0, canvas.width, canvas.height);
                   
                    //update all dots
                    for (var i=0; i < dots.length; i++){
                        context.beginPath();
                        context.arc(dots[i].x, dots[i].y, dots[i].radius, 0, Math.PI * 2, false);
                        context.fillStyle = dots[i].color;
                        context.fill();
                        context.closePath();
                    }
                }
               
                setInterval(function() {
                    time += 100;
                    if(time >= 16000) time = 0;
                });
               
                setInterval(function() {
                    update();
                    draw();
                }, 1000/fps);
               
                $(window).resize(function(){
                    canvas.width = $(window).width();
                    canvas.height = $(window).height();
                });
               
            });​
        • Thomas
          Posted March 12, 2012 at 5:17 am | Permalink

          @Evan:

          Thanks for your advice! My first approach was built with paper.js… but i’ve noticed some performance issues on smartphones. that’s why i’m also here… Need to recode it in pure javascript.

          Have a look here http://ntro-design.de/spielplatz/
          The Goal is to build a smooth animation which fits to some background music.

          Funny feature would be something like this onclick function here:
          http://ntro-design.de/spielplatz/index2.php

          Which you ve shown before in one of your Interactive Physics Animations Javascript Canvas Series :)

          • Thomas
            Posted March 12, 2012 at 5:18 am | Permalink

            @Evan:

            Forgot to say, best viewed in Chrome…

          • Posted March 14, 2012 at 2:23 pm | Permalink

            @Thomas – Very nice. I has a very good sense of atmosphere. I guess music will do that. The click is fun too.

            Are you getting a sense of how much faster using your own script vs using a library to do your animations? I’m curious as to what you’re finding.

    Post a Comment

    Your email is never published nor shared. Required fields are marked *

    *
    *

    You may use these HTML tags and attributes: <a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

    • Recent Posts

      Touch Specific Media Queries in CSS4

      Proposals in the works for new media queries specific to touch enabled devices. Examples include pointer, which will differentiate wether the device has a fine or coarse pointer (finger vs mouse) and hover, which would say if the device supports hover states. I can see this being helpful and useful for building mobile friendly sites [...]

      Responsive Image Dispute and Tourists – Analogy

      Jason explains the root of the problem and why no one has been able to devise a solution that makes everyone happy yet. The browsers (in their awesome drive to make browsing faster) are prefetching images and developers want to only use one image based on criteria the browser doesn't know until the layout is [...]

      Google Moog Keyboard Synthesizer Detailed

      This post on creativejs picks apart how this doodlue was made. Nice they they are able to support HTML5 audio even if it is only supported on chrome and the rest fall back to – you guessed it – flash. Embedded Link Google Moog synth tear-down Yesterday we featured Google's web-based analogue synth Moog tribute [...]

      Synth Emulator on Google Japan Doodle Today

      Synth Emulator on Google Japan Doodle Today Embedded Link Moog 自分のオリジナル曲を創って、 #moogdoodle で共有しよう。 Tweet

      WordPress updates plugin directory

      New additions to the plugin directory include: favorites, incorporating support forums into it's own tab for each plugin as well as support stats being displayed! Great! I think we also need the ability to give plugins ratings and reviews (bonus points if it can be done from within a wordpress admin dashboard when installing plugins). [...]

      Short Head

      Use zipf's short head to tune your website rather than redesign the whole thing. To make a website successful it needs to meet the needs of the users. Find out what those needs are by using the short head philosophy to equate most searched things as the biggest needs of the users. Use personas to [...]

      Img Set?

      Great article at a list apart discusing the state of the industry regarding responsive images. This picks apart the set attribute of the img element from a surprisingly objective view coming from someone so close to the picture element. Insightful discussion about the principle behind the proposals than the actual solution too. If the working [...]

      Triudo

      A mesmerizing animated triangle-ish shape form. Embedded Link triduo triduo Tweet

      Git – the paradigm shift

      A great developer story about the differences on what Git is vs other version control and what Git is not. This is how we should learn it. I heard over and over that it was distributed, but never grasped what that meant, so here are a few links and explanations that will help unlearn version [...]

      Tweening Lib comes to Javascript!

      I'm very excited to share the news that the tween library from GreenSock (hands down the best tweening library I used in flash) is not ported for use in javascript! This will be great! I missed that simple syntax from as3 when animating javascript, and now I can have my cake and eat it too. [...]

    • Recent Comments

      Bruce Brownlee

      Bruce Brownlee

      Ah IE6. I'd have 2 more years of sleep without IE6. Margin doubling, no properties,...
      versaena

      versaena

      how to give color at runtime…… thank you
      Mobile Websites

      Mobile Websites

      I disagree, mobile websites are the future – desktop websites and mobile websites...
      Matt Fasick

      Matt Fasick

      That's cool. I like the ripple effect as well.
      Nico

      Nico

      hi! really great job guy! very impressive.. just a question… do u have a solution to do a refresh...
      Evan Mullins

      Evan Mullins

      Agreed! I've just seen some people get pretty heated about separating all functionality...