<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body onload="init();">
<canvas id="myCanvas" width=500 height=500>
</canvas>
<video id="myVideo" autoplay controls>
<source src="http://mainline.i3s.unice.fr/mooc/mancity.mp4">
</video>
<button id="playpause">Play/Pause</button>
<p>
<button id="boutonAddArret">Marquer un arrêt</button>
</body>
</html>
#myCanvas {
border: 2px solid black;
background-image:url('https://download.tuxfamily.org/gimplovers/vignettes/tutoriels/textures/texture-crepi.jpg');
}
#myVideo {
display:none;
}
// Bonne pratique : on déclare le canvas en global
var canvas;
var ctx;
var video;
var mousepos;
var drawingArrow;
var startArrow = {};
var endArrow = {};
var tableauArrows = [];
var tableauDesTempsDarrets = [];
var prochainArretIndex = 0;
var sauteProchainArret = false;
function init() {
// Appelée dès que la page est chargée...
//console.log('page chargée');
// Pointeur sur le canvas
canvas = document.querySelector("#myCanvas");
// Pour dessiner il faut récupérer un autre objet
// qu'on obtient à partir du canvas : le contexte
// graphique
ctx = canvas.getContext("2d");
// autre possibilité : "webgl" pour la 3D
// l'élément video
video = document.querySelector("#myVideo");
// ecouteur sur l'évenement timeupdate
video.addEventListener("timeupdate", videoTimeupdate, false);
// Bouton playpause, écouteur
var boutonPlayPause = document.querySelector("#playpause");
var boutonAddArret = document.querySelector("#boutonAddArret");
boutonPlayPause.onclick = function() {
console.log("play / pause");
boutonAddArret.disabled=false;
if(video.paused) {
video.play();
} else {
video.pause();
}
}
// Bouton marquer un arrêt (va ajouter des trucs à afficher et forcer la pause)
boutonAddArret.onclick = function() {
console.log("add arrêt");
// si la video n'est pas en pause on la pause
video.pause();
// mémoriser le temps
// on utilise un tableau dans lequel on
// ajouter les temps où la vidéo devra
// s'arrêter
tableauDesTempsDarrets.push(video.currentTime);
tableauDesTempsDarrets.sort(compareNombres);
// on desactive le bouton tant qu'on a pas refait play
boutonAddArret.disabled=true;
// Si sauteProchainArret = true alors quand on cliquera
// sur play/pause on ne s'arrêtera pas de suite
sauteProchainArret = true;
}
function compareNombres(a, b) {
return a - b;
}
// Ecouteurs de souris
window.addEventListener("mousedown", traiteMousedown, false);
window.addEventListener("mouseup", traiteMouseup, false);
window.addEventListener("mousemove", traiteMousemove, false);
anime();
}
function videoTimeupdate(evt) {
// On regarde s'il y a un prochain arrêt prévu
if(prochainArretIndex !== -1){
console.log("time = " + video.currentTime + " i = " + prochainArretIndex + " t = " + tableauDesTempsDarrets[prochainArretIndex]);
if(video.currentTime > tableauDesTempsDarrets[prochainArretIndex]) {
if(!sauteProchainArret) video.pause();
sauteProchainArret = false;
}
}
prochainArretIndex = chercheProchainArret(video.currentTime);
}
// on a [2, 3, 7, 9] et time = 6
// la bonne réponse est 3, l'index de la valeur 7
function chercheProchainArret(time) {
var index=-1;
for(index=0; index < tableauDesTempsDarrets.length; index++) {
var elem = tableauDesTempsDarrets[index];
if(time <= elem) {
//console.log("index trouvé : " + index)
return index;
}
}
// Si on a parcouru tout le tableau sans trouver
if(index == tableauDesTempsDarrets.length)
return -1;
}
function traiteMouseup(evt) {
//console.log("mouseup");
drawingArrow = false;
// on enregistre la flèche dans le tableau
// des flèches à dessiner
tableauArrows.push({x1 : startArrow.x,
y1 : startArrow.y,
x2 : endArrow.x,
y2 : endArrow.y});
}
function traiteMousedown(evt) {
//console.log("mousedown");
drawingArrow = true;
startArrow.x = mousePos.x;
startArrow.y = mousePos.y;
}
function traiteMousemove(evt) {
//console.log("mousemove");
mousePos = getMousePos(canvas, evt);
endArrow.x = mousePos.x;
endArrow.y = mousePos.y;
}
function getMousePos(canvas, evt) {
// necessary to take into account CSS boundaries
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
// x, y, rx, ry, angle en degrés, largeur, couleur en hexa
var e = new EllipseAnimee(100, 100, 60, 30, 96.5, 7, "#6699CC");
// Si on appelle que drawPlain, on peut utiliser n'importe quelle
// syntaxe CSS pour la couleur
var e1 = new EllipseAnimee(130, 230, 120, 30,95, 15, "yellow");
e1.portionAngle = 2*Math.PI/3;
e1.vitesseAnimation = 0.3;
var e3 = new EllipseAnimee(220, 150, 60, 30, 95, 17, "#690CC");
e3.vitesseAnimation = 0.15;
e3.portionAngle = Math.PI;
function anime() {
// 1 - On efface le canvas
//ctx.clearRect(0, 0, canvas.width, canvas.height);
// 1 - effacer les deux canvas : celui avec la video (en
// fait on ne l'efface pas, on dessine l'image de la
// video), et le front canvas, celui où on dessine
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
// 2 - On dessine des trucs
tete.draw();
// dessiner les anciennes fleches
tableauArrows.forEach(function(elem) {
drawArrow(ctx,
elem.x1, elem.y1,
elem.x2, elem.y2,
10, "red");
});
// dessiner la flèche en cours
if(drawingArrow) {
drawArrow(ctx,
startArrow.x, startArrow.y,
endArrow.x, endArrow.y,
10, "green");
}
// Test : dessiner une ellipse
//drawEllipse(ctx, 100, 100, 100, 40, 4, "red");
e.drawDegrade(ctx);
e.update();
e1.drawPlain(ctx);
e1.update();
e3.drawDegrade(ctx);
e3.update();
// 3 - on deplace des trucs
tete.move(0.2, 0);
// 3.5 on gère les interactions (avec l'utilisateur)
// ou entre les objets.
testCollisionsAvecMurs();
// 4 - on recommence mais 60 fois par seconde
requestAnimationFrame(anime);
}
// On fait un objet singleton pour le monstre
var tete = {
x:100,
y:100,
largeur: 100,
hauteur:100,
vitesseX : 1,
vitesseY : 3,
draw: function() {
// Bonne pratique :
// Toujours sauvegarder le contexte quand
// dans une fonction on modifie quelque chose
// du contexte : couleur, épaisseur du trait etc.
// Ou le repère dans lequel on travaille
// et on le restaure à fin
// sauver le contexte
ctx.save();
ctx.translate(this.x-10-this.largeur/2, this.y-10-this.hauteur/2);
//ctx.rotate(0.2);
//ctx.scale(0.5, 0.5);
// Tete
ctx.strokeRect(10, 10, this.largeur, this.hauteur);
// yeux rouges
ctx.fillStyle = "red";
ctx.fillRect(30, 30, 10, 10);
ctx.fillRect(75, 30, 10, 10);
// petits yeux dans les gros yeux
ctx.fillStyle = "black";
ctx.fillRect(34, 35, 5, 5);
ctx.fillRect(78, 32, 5, 5);
// nez violet
ctx.fillStyle = "purple";
ctx.fillRect(53, 45, 10, 35);
// bouche orange
ctx.fillStyle = "orange";
ctx.fillRect(38, 85, 40, 10)
// dents noires
ctx.fillStyle = "black";
ctx.fillRect(44, 85, 5, 10);
ctx.fillRect(54, 85, 5, 10);
ctx.fillRect(64, 85, 5, 10);
ctx.restore();
},
move: function() {
this.x += this.vitesseX;
this.y += this.vitesseY;
}
}
function testCollisionsAvecMurs() {
// à droite
if((tete.x + tete.largeur/2)>= canvas.width) {
tete.vitesseX = -tete.vitesseX;
// On repositionne au point de contact
tete.x = canvas.width-tete.largeur/2;
}
// à gauche
if((tete.x - tete.largeur/2) <=0) {
tete.vitesseX = -tete.vitesseX;
tete.x = tete.largeur/2;
}
// en bas
if((tete.y + tete.hauteur/2)>= canvas.height) {
tete.vitesseY = -tete.vitesseY;
// On repositionne au point de contact
tete.y = canvas.height-tete.hauteur/2;
}
if((tete.y - tete.hauteur/2) <=0) {
tete.vitesseY = -tete.vitesseY;
tete.y = tete.hauteur/2;
}
}
function drawArrow(ctx, fromx, fromy, tox, toy, arrowWidth, color){
//variables to be used when creating the arrow
var headlen = 10;
var angle = Math.atan2(toy-fromy,tox-fromx);
ctx.save();
ctx.strokeStyle = color;
ctx.shadowColor = "Black"; // color
ctx.shadowBlur = 10; // blur level
ctx.shadowOffsetX = 5; // horizontal offset
ctx.shadowOffsetY = 5;
//starting path of the arrow from the start square to the end square
//and drawing the stroke
ctx.beginPath();
ctx.moveTo(fromx, fromy);
ctx.lineTo(tox, toy);
ctx.lineWidth = arrowWidth;
ctx.stroke();
//starting a new path from the head of the arrow to one of the sides of
//the point
ctx.beginPath();
ctx.moveTo(tox, toy);
ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),
toy-headlen*Math.sin(angle-Math.PI/7));
//path from the side point of the arrow, to the other side point
ctx.lineTo(tox-headlen*Math.cos(angle+Math.PI/7),
toy-headlen*Math.sin(angle+Math.PI/7));
//path from the side point back to the tip of the arrow, and then
//again to the opposite side point
ctx.lineTo(tox, toy);
ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),
toy-headlen*Math.sin(angle-Math.PI/7));
//draws the paths created above
ctx.stroke();
ctx.restore();
}
// ellipses
function drawEllipse(ctx, cx, cy, l, h, lw, couleur) {
ctx.save();
ctx.beginPath();
ctx.moveTo(cx, cy - h/2); // A1
ctx.bezierCurveTo(
cx + l/2, cy - h/2, // C1
cx + l/2, cy + h/2, // C2
cx, cy + h/2); // A2
ctx.bezierCurveTo(
cx - l/2, cy + h/2, // C3
cx - l/2, cy - h/2, // C4
cx, cy - h/2); // A1
ctx.lineWidth = lw
ctx.strokeStyle = couleur;
ctx.stroke();
ctx.closePath();
ctx.restore();
}
/*
centre : x et y
rayon horizontal = r1
rayon vertical = r2
angle en degré, on tourne dans le sens des aiguilles d'une montre
lt = largeur du trait
couleur = couleur
Pour dessiner : appeler la méthode draw(ctx)
Pour animer : appeler update depuis une boucle d'animation
*/
function EllipseAnimee(x, y, r1, r2, angle, lt, couleur) {
this.x = x;
this.y = y;
this.r1 = r1;
this.r2 = r2;
this.angle = angle*Math.PI/180;
this.lt = lt;
this.angleDepart = 0;
this.portionAngle=Math.PI/3;
this.vitesseAnimation = 0.07;
this.couleur = couleur;
this.drawPlain = function(ctx) {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
ctx.scale(this.r2/this.r1,1);
ctx.lineWidth = lt;
ctx.strokeStyle = this.couleur;
ctx.beginPath();
ctx.arc(0, 0,
this.r1,
this.angleDepart,
this.angleDepart + this.portionAngle, false);
ctx.stroke();
ctx.restore();
}
this.drawDegrade = function(ctx) {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
ctx.scale(this.r2/this.r1,1);
var lum=0.05;
for(var a=this.angleDepart; a < Math.PI*2+this.angleDepart-0.1; a+=0.05) {
var px = 0 + this.r1 * Math.cos(a);
var py = 0 + this.r1 * Math.sin(a);
ctx.beginPath();
if(lum == 0.05) {
ctx.arc(px, py,
this.lt,
0,
Math.PI*2,
false);
} else {
ctx.arc(px, py,
this.lt/2,
0,
Math.PI*2,
false);
}
// changer 80 en autre chose pour que la variation de
// couleur soit plus forte/moins forte
ctx.fillStyle = colorLuminance(this.couleur, lum);
lum += 0.01;
ctx.fill();
}
ctx.restore();
}
this.update = function() {
this.angleDepart += this.vitesseAnimation;
}
}
// hex = la couleur en hexadecimal ex : "#00FFFF"
// lum = une valeur entre -1 et +1
// si négatif = plus clair. Par ex lum=-0.5 = 50% plus
// clair. Voir https://www.sitepoint.com/javascript-generate-lighter-darker-color/
function colorLuminance(hex, lum) {
// validate hex string
hex = String(hex).replace(/[^0-9a-f]/gi, '');
if (hex.length < 6) {
hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
}
lum = lum || 0;
// convert to decimal and change luminosity
var rgb = "#", c, i;
for (i = 0; i < 3; i++) {
c = parseInt(hex.substr(i*2,2), 16);
c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
rgb += ("00"+c).substr(c.length);
}
return rgb;
}
Output
You can jump to the latest bin by adding /latest
to your URL
Keyboard Shortcuts
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + ] | Indents selected lines |
ctrl + [ | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + shift + L | Beautify code in active panel |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Open the share options |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
JS Bin URLs
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |