Tutorial: Bouncing video



Tutorial: Bouncing video

This is [the start of] a tutorial describing an HTML5 JavaScript application in which a video, masked to appear as a circle, bounces in a 2-D box. The working program is at

This code works on Firefox, Chrome and Opera. It was necessary to make a few changes to make it work on Chrome that will be noted below.

Plan

The application can be divided into four challenges:

• making something appear to bounce in a 2-D box, done using standard coding and the setInterval command;

• getting video into an html document AND ensuring that the video or a black rectangle does not appear in the original location, accomplished by CSS. Making the video element visibility: hidden worked for Firefox. Using display: none worked for both Firefox and Chrome;

• getting running video to appear in a specific spot on the screen, accomplished here by using drawImage to position the video in a canvas element;

• masking video so it appears to be circular in shape, accomplished here by using beginPath and fill to draw a white shape that rides along over the video. A A mask made by a filled path of a circle inside a rectangle worked in Firefox, but not Chrome. Instead, I made two paths, shown below.

Bouncing in a box

The setInterval command is a part of standard HTML that provides a way to invoke a function at each interval of time. This can be used to re-position an object, call it the ball, on the screen. A canvas element in HTML is a canvas, not a Stage in which individual objects can be re-positioned. However, we can write code to erase (clear) the canvas and then re-draw things such as video, paths and rectangles, at the intervals of time. This is what is done in the function drawscene. The init function has the line:

setInterval(drawscene,50);

This sets up a call to the function every 50 milliseconds (20 times/second).

The drawscene function does the drawing after a call to moveandcheck to do the movement calculations. The movement of the ball (the thing being bounced) is accomplished using displacement values for the changes in the horizontal (x) coordinate and in the vertical (y) coordinates. The bouncing is accomplished by calculating if the ball position would hit (collide) with any of the four walls. Depending on which wall, the vertical or the horizontal displacement value is changed in sign. To put this another way, if the ball [virtually] hits a vertical wall, then we want the horizontal displacement value to change at the next iteration. If the ball [virtually] hits a horizontal wall, then we want the vertical displacement value to change at the next iteration.

The moveandcheck function does a tentative move, using ballx, bally for the position, and ballvx and ballvy for the displacement variables:

var nballx = ballx + ballvx;

var nbally = bally + ballvy;

and then checks the nballx and nbally values against the walls in 4 if statements, using variables representing the inner and outer limits of each wall. See full code in the Implementation section. There are sets of variables: ballx, bally, boxx, boxy, boxboundx, boxboundy, inboxboundx, inboxboundy that I developed to get the effects that I wanted: the ball object hits and goes behind (or seems to compress) and then bounces off. You can experiment with these to get the effects you want. Any hit will cause the appropriate position variables and displacement variables. After the if statements, the ball is re-positioned:

ballx = nballx;

bally = nbally;

The calculations done by the if statements are what is needed to make the ball bounce. This is independent of displaying the walls of the box. That is done using HTML5 methods of a canvas context. In my code, ctx is a variable set early on to hold the context. The statements:

ctx.lineWidth = ballrad;

ctx.strokeStyle ="rgb(200,0,50)";

ctx.strokeRect(boxx,boxy,boxwidth,boxheight);

set a line width, the color of the walls, and then draw the walls as an outline of a rectangle with upper left corner at boxx, boxy and with width boxwidth, and height boxheight. The stroke/line of the rectangle is relatively big. This is to prevent the ball from appearing on the outside of the box.

There are two frame-like operations going on here: that caused by the setInterval command and that of the video. There may be some interference that may need to be fixed in some situations.

You can build on this application by putting in forms with fields for varying the speed by changing ballvx and ballvy and implementing the ball slowing down when it hits a wall, by not simply changing the sign, but coding more complex expressions.

Displaying video in HTML5 and hiding the static video element

HTML5 provides a way to display video! A video element specifies the video source. Because of lack of agreement on what encoding (codec) to use for video, a recommended technique, supported by HTML5, is to create and specify multiple video files, each a version of the same video. The following is the video element used in this application.

Your browser does not accept the video tag.

Note: the loop setting does not work in Firefox so I used code to re-start the video. The preload setting indicates that the video is to start playing when it is loaded. I include code to play the video just in case this doesn't work. Lastly, though I have NOT tested it, and the spec says that the order should not matter, I have read that the webm source should be mentioned first for the video to play on an iPad. These are all matters that will sort themselves out over time.

You can view in a browser:

I have added controls="controls" to the tag in this document to get controls displayed. I do not want controls in the bouncing video. Remember for this and all the examples, you can use view source to see the HTML source file.

The above code would be a way to display video in a fixed position in an HTML5 document. However, that is not what we want for the bouncing video. Instead the code will use the video element in a drawImage command. In the style element, I use CSS to set positioning to be absolute, set the original position, and make it NOT display, and also set the positioning of the canvas element and make z-index settings so the canvas is on top of the video:

#vid {position:absolute; display:none; z-index: 0;

left: 50px;

top: 60px;

}

#canvas {position:absolute; z-index:10;}

Video on canvas

So now the task is to get video to be drawn on the canvas. The drawImage method can be used with images or video. The code accesses (my term, may not be the best) the video element and uses it in a drawImage command. The code in the init function (invoked when the document is loaded) is

v = document.getElementById("vid");

v.addEventListener("ended",restart,false);

v.width = v.videoWidth/3;

v.height = v.videoHeight/3;



v.play();

This code sets the variable v for later use. The addEventListener sets up a function named restart to be invoked when the video ends. The restart function does just that: restart the video. Note: there appears to be no problem using this in Chrome, which does recognize the loop setting. Then v.width and v.height are set to be 1/3 the original settings. This has the effect of shrinking the video in a way that preserves the aspect ratio.

As indicated in the section on Bouncing, the drawscene function draws the ball object at a calculated position on the canvas. The variable ctx has been set to be what is termed the context of the canvas. The variable v is set to be the video element. The line:

ctx.drawImage(v,ballx+boxx, bally+boxy, v.width,v.height);

draws the video at the indicated position and with the width and height as specified.

Creating circular shape for video

The video appears as a circle in my program because the code creates something on top of it. This is termed a mask. Some applications, for example, PhotoShop, have built in mask structures. I created this mask-like shape on my own. I use the attributes of the video element to determine the size of the mask. In the init function, this code appears:

videow = v.width;

videoh = v.height;

maskrad = .4*Math.min(videow,videoh);

My initial idea for a mask was a path consisting of 4 lines that essentially were a rectangle, but not generated by strokeRect, and then an circle generated by arc. The shape had a hole:

ctx.beginPath();

ctx.moveTo(ballx+boxx,bally+boxy);

ctx.lineTo(ballx+boxx+v.width,bally+boxy);

ctx.lineTo(ballx+boxx+v.width,bally+boxy+v.height);

ctx.lineTo(ballx+boxx,bally+boxy+v.height);

ctx.lineTo(ballx+boxx,bally+boxy);

ctx.arc(ballx+boxx+.5*v.width,bally+boxy+.5*v.height,

maskrad,0,Math.PI*2,true);

ctx.fill();

This worked for Firefox, but the fill command did not work properly in Chrome. Instead I created the same mask with two shapes for this effect. For illustration, I produced this picture using ctx.stroke() inplace of ctx.fill().

[pic]

The actual two shapes are produced using a fillStyle of white and no stroke so no lines appear.

The mask shape travels along with the video as shown in the drawscene code in the Implementation section.

Implementation

The application consists of four functions, described in the following table:

|function |called/invoked by |calls |task |

|init |action of onload in |drawscene |set variables, event handling |

| | | |(video ending and timed |

| | | |interval event) |

|restart |action of addEventListener in init for | |loop video |

| |"ended" event | | |

|drawscene |called directly in init and then by |moveandcheck |draw moving video (including |

| |action of setInterval in init | |mask) after call to calculate |

| | | |new position. |

|moveandcheck |drawscene | |calculates new position and |

| | | |checks if ball hits any walls |

Note: the moveandcheck code could have been part of drawscene but I made it its own function.

| |standard boilerplate |

| |open html |

| |open head |

|Video bounce |title of document |

| |standard boilerplace |

| |open style |

|#vid {position:absolute; display:none; z-index: 0; |set absolute positioning, make invisible,|

| |set lowest z (layering) |

|left: 80px; |set at initial x of ball |

|top: 90px; |set at initial y of ball |

|} |close the style for the video |

|#canvas {position:absolute; z-index:10;} |set absolute positioning, higher z index |

| |to be on top of video |

| |close style |

| |standard boilerplate for javascript |

| var ctx; |will hold context, used for all drawing |

| var cwidth = 900; |width of canvas |

| var cheight = 600; |height of canvas |

| var ballrad = 50; |nominal ball radius |

| var boxx = 30; |starting x of box |

| var boxy = 30; |starting y of box |

| var boxwidth = 850; |box width |

| var boxheight = 550; |box height |

| var boxboundx = boxwidth+boxx-2*ballrad; |for comparisons |

| var boxboundy = boxheight+boxy-2*ballrad; |for comparisons |

| var inboxboundx = -10; |for comparisons |

| var inboxboundy = 0; |for comparisons |

| var ballx = 50; |initial x within box |

| var bally = 60; |initial y within box |

| var maskrad; |will be set based on video |

| var ballvx = 2; |initial x displacement |

| var ballvy = 4; |initial y displacement |

| var v; |will hold video element |

| | |

|function restart() { |function header for the restart function |

| v.currentTime=0; |set position in video |

| v.play(); |play the video |

|} |close restart function |

| | |

|function init(){ |function header for init function |

| ctx = document.getElementById('canvas').getContext('2d'); |set ctx |

| v = document.getElementById("vid"); |set v |

| v.addEventListener("ended",restart,false); |does looping |

| v.width = v.videoWidth/3; |set width based on original width |

| v.height = v.videoHeight/3; |set height based on original height |

| videow = v.width; |used for calculation |

| videoh = v.height; |used for calculation |

| maskrad = .4*Math.min(videow,videoh); |set mask radius to be 40% of the minimum |

| |of width and height |

| ctx.fillStyle="white"; |set for white mask |

| ctx.strokeStyle ="rgb(200,0,50)"; |set color for walls |

|} |close init |

|function drawscene(){ |function header for drawscene |

| ctx.clearRect(0,0,boxwidth+boxx,boxheight+boxy); |clear canvas |

| moveandcheck(); |do tentative move, check and then move |

| ctx.drawImage(v,ballx+boxx, bally+boxy, v.width,v.height); |draw the video |

| ctx.beginPath(); |begin path for the mask |

| ctx.moveTo(ballx+boxx,bally+boxy); |move to corner |

| ctx.lineTo(ballx+boxx+v.width,bally+boxy); |prepare line across |

| ctx.lineTo(ballx+boxx+v.width,bally+boxy+.5*v.height); |prepare line half way down |

| ctx.lineTo(ballx+boxx+.5*v.width+maskrad, bally+boxy+.5*v.height); |prepare line to edge of hole |

| ctx.arc(ballx+boxx+.5*v.width,bally+boxy+.5*v.height,maskrad,0,Math.PI,true); |prepare arc (half circle..a frown) |

| ctx.lineTo(ballx+boxx,bally+boxy+.5*v.height); |prepare line to edge |

| ctx.lineTo(ballx+boxx,bally+boxy); |prepare line to start |

| ctx.fill(); |fill in this shape |

| ctx.moveTo(ballx+boxx,bally+boxy+.5*v.height); |move half way down |

| ctx.lineTo(ballx+boxx,bally+boxy+v.height); |prepare line down |

| ctx.lineTo(ballx+boxx+v.width,bally+boxy+v.height); |prepare line over to left |

| ctx.lineTo(ballx+boxx+v.width,bally+boxy+.5*v.height); |prepare line halfway up |

| ctx.lineTo(ballx+boxx+.5*v.width+maskrad,bally+boxy+.5*v.height); |prepare line to edge of hole |

| ctx.arc(ballx+boxx+.5*v.width,bally+boxy+.5*v.height,maskrad,0,Math.PI,false); |prepare arc (half circle… a smile) |

| ctx.lineTo(ballx+boxx,bally+boxy+.5*v.height); |prepare line to start |

| ctx.fill(); |fill in this shape |

|ctx.strokeRect(boxx,boxy,boxwidth,boxheight); |draw box |

|} |close drawscene |

|function moveandcheck() { |function header for function that |

| |calculates the new position |

| var nballx = ballx + ballvx; |set tentative new x |

| var nbally = bally +ballvy; |set tentative new y |

| | |

| if (nballx > boxboundx) { |compare on right side |

| ballvx =-ballvx; |if hit, change horizontal displacement |

| nballx = boxboundx; |…set x to exactly at right boundary |

| } |close clause |

| if (nballx < inboxboundx) { |compare on left side |

| nballx = inboxboundx |if hit, set x exactly at left boundary |

| ballvx = -ballvx; |change horizontal displacement |

| } |close clause |

| if (nbally > boxboundy) { |compare to bottom |

| nbally = boxboundy; |if hit, set y exactly at bottom boundary |

| ballvy =-ballvy; |…change vertical displacement |

| } |close clause |

| if (nbally < inboxboundy) { |compare on top |

| nbally = inboxboundy; |if hit, set y to be exactly top |

| ballvy = -ballvy; |change vertical displacement |

| } |close clause |

| ballx = nballx; |set ballx |

| bally = nbally; |set bally |

|} |close moveandcheck |

| |close script |

| |close head |

| |set up call to init |

| |set up video, loop and run upon loading. |

| |type of codecs |

| |type of codecs |

| |type of codecs |

|Your browser does not accept the video tag. |warning message for non-compliant |

| |browsers |

| |close video |

| |set up canvas |

|This browser doesn't support the HTML5 canvas element. |warning message for non-compliant |

| |browsers |

| |close canvas |

| |close body |

| |close html |

................
................

In order to avoid copyright disputes, this page is only a partial summary.

Google Online Preview   Download