Oggi proveremo a disegnare sul canvas di html5 con jQuery. Sarà un semplice esempio per iniziare a vedere cosa succede.
Nella pagina mettiamo un canvas, giusto con un bordino csse poi ci concentreremo sugli eventi legati al mouse.
Abbiamo bisgon di riferirci al canvas e impostare una variabile booleana draw a false:
var context = $('#target')[0].getContext('2d'); var draw = false;
Poi ci interessano 4 eventi legati al mouse, due posizioneranno la variabile draw a false (mouseup, mouseout) e uno a true (mousedown).
Quindi se abbiamo premuto e siamo dentro al canvas con mousemove disegniamo prendendo le coordinate del punto in cui sitrova il mouse per tracciare la linea.
$('#target') .mousedown(function() { draw = true; }) .mouseup(function() { draw = false; }) .mouseout (function() { draw = false; }) .mousemove(function(event) { var pos = $(this).position(); if(draw){ context.fillRect(event.pageX - pos.left, event.pageY - pos.top, 2, 2); } });
L’esempio
Quando si esegue gli script in una pagina HTML, la pagina non risponde fino a quando lo script è terminato e dato l’ampio uso che si fa oggi di javascript la cosa a volte inizia a diventare problematica.
Un web workers è un JavaScript che viene eseguito in background, indipendentemente da altri script, senza compromettere le prestazioni della pagina. È possibile continuare a fare quello che vuoi: cliccare, selezionare cose, mentre il Web Worker viene eseguito in background.
Prepariamo dunque il nostro web worker:
var i=0; function timedCount(){ i=i+1; postMessage(i); setTimeout("timedCount()",1000); } timedCount();
Si tratta di un semplice contatore che richiama se stesso ogni secondo grazie al timeout, postMessage() aonsente la comunicazione tra i due documenti, indipendentemente dalla loro collocazione.
La pagina html sarà composta da due bottoni per avviare e fermare il web worker e un campo per l’output, tramite il tag put put di html5 che serve proprio per inserire il risultato di un calcolo.
<p>Count numbers: <output id="result"></output></p> <button onclick="startWorker()">Start Worker</button> <button onclick="stopWorker()">Stop Worker</button>
Quello che segue sarà nell’ordine il controllo del supporto da parte dei browser, il controllo sull’inizializzazione dell’oggetto, la creazione dell’oggetto e la gestione dell’output o viceversa il messaggio di errore in caso di mancato supporto.
var w; function startWorker(){ if(typeof(Worker)!=="undefined"){ if(typeof(w)=="undefined"){ w=new Worker("js/demo_workers.js"); } w.onmessage = function (event) { document.getElementById("result").innerHTML=event.data; }; } else{ document.getElementById("result").innerHTML="Il tuo browser non supporta Web Workers..."; } }
Ultimo step l’arresto del web worker:
function stopWorker(){ w.terminate(); }
L’esempio
Probabilmente una cosa che molti sviluppatori hanno sempre sognato di fare è l’upload dei files sul server trascinandoli direttamente dal proprio desktop, o dal file system più in generale, nel browser. Ecco con html5 questa cosa finalemnte si potrà fare, vediamo come.
Partiamo dall’html:
<section id="wrapper"> <header><h1>Drag and drop, upload automatico</h1></header> <article> <div id="holder"></div> <p id="upload"><label>Drag & drop non supportato, procedi attraverso il campo di input:<br><input type="file"></label></p> <p id="filereader">File API & FileReader API non supportato</p> <p id="formdata">XHR2 FormData non supportato</p> <p id="progress">XHR2 upload progress non supportato</p> <p>Upload progress: <progress id="uploadprogress" min="0" max="100" value="0">0</progress></p> <p>Trascina un'immagine dal tuo desktop nella drop zone, vedrai nel browser il render della preview, ma verrà anche automaticamente fatto l'upload sul server.</p> </article> </section>
Oltre al titolo abbiamo il div id=”holder” che sarà l’aea di rilascio dell’oggetto; quattro paragarafi contenenti i messaggi di errore relativi a funzionalità html5 che potrebbero non essere supportate da tutti i browser, il tag progress per la gestione della progress bar relativa all’upload e un paragrafo di istruzioni.
Rapidamente anche il css, o almeno una parte:
#holder { border: 10px dashed #ccc; width: 300px; min-height: 300px; margin: 20px auto;} #holder.hover { border: 10px dashed #0c0; } #holder img { display: block; margin: 10px auto; } #holder p { margin: 10px; font-size: 14px; } progress { width: 100%; } progress:after { content: '%'; } .fail { background: #c00; padding: 2px; color: #fff; } .hidden { display: none !important;}
Abbastanza importante la formattazione di #holder che deve rendere facilmente riconoscibile la drop zone,
per il resto da notare la classe .hidden per nascondere le funzionalità supportate.
Ma veniamo al cuore javascript del nostro lavoro e iniziamo con la definizione delle variabili che ci serviranno:
var holder = document.getElementById('holder'), tests = { filereader: typeof FileReader != 'undefined', dnd: 'draggable' in document.createElement('span'), formdata: !!window.FormData, progress: "upload" in new XMLHttpRequest }, support = { filereader: document.getElementById('filereader'), formdata: document.getElementById('formdata'), progress: document.getElementById('progress') }, acceptedTypes = { 'image/png': true, 'image/jpeg': true, 'image/gif': true }, progress = document.getElementById('uploadprogress'), fileupload = document.getElementById('upload');
Oltre ai document.getElementById ci interessa la definizione dei test che dovremo attuare prima di poter procedere cioè relativi al supporto delle API FileReader, Drag&Drop, FormData e del tag progress e dei tipi di dato, immagini, accettate dal nostro uploader. Aquesto punto possiamo eseguire il test:
"filereader formdata progress".split(' ').forEach(function (api) { if (tests[api] === false) {support[api].className = 'fail';} else {support[api].className = 'hidden'; } });
Mettiamo i test da compiere in una stringa, la splittiamo creando un’array sul quale fare un ciclo foreach, se il test fallisce applichiamo la classe css fail per evidenziare il paragrafo, viceversa lo nascondiamo. Aquesto punto se il nostro browser supporta le funzionalità necessarie possiamo procedere. O meglio attuiamo l’ultimo test sul drag&drop e gestiamo il tutto:
if (tests.dnd) { holder.ondragover = function () { this.className = 'hover'; return false; }; holder.ondragend = function () { this.className = ''; return false; }; holder.ondrop = function (e) { this.className = ''; e.preventDefault(); readfiles(e.dataTransfer.files); } } else { fileupload.className = 'hidden'; fileupload.querySelector('input').onchange = function () { readfiles(this.files); }; }
Ovvero se il test fallisce passiamo alla modalità input tradizionale, altrimenti associamo agli eventi dragover e dragend le opprtune classi css prima definite, ma soprattutto all’ondrop chiamiamo la funzione readfiles() assando come argomento il file caricato. La funzione readiles dovrà fare 3 cose: mostrare il file nella preview e di questo si occuperà un’apposita funzione, passare il file al server e infine gestire la progress bar.
Procediamo in ordine e iniziamo dalla preview:
var formData = tests.formdata ? new FormData() : null; for (var i = 0; i < files.length; i++) { if (tests.formdata) formData.append('file', files[i]); previewfile(files[i]); } function previewfile(file) { if (tests.filereader === true && acceptedTypes[file.type] === true) { var reader = new FileReader(); reader.onload = function (event) { var image = new Image(); image.src = event.target.result; image.width = 250; // a fake resize holder.appendChild(image); }; reader.readAsDataURL(file); } else { holder.innerHTML += '<p>Uploaded ' + file.name + ' ' + (file.size ? (file.size/1024|0) + 'K' : ''); console.log(file); } }
Nel caso specifico quindi la funzione si occupa di testare il supporto dell’API, che l’immagine sia fra quelle accettate e infine di “appenderla” nella pagina a una larghezza fissa di 250px
Il secondo step è costituito dall’upload del file tramite ajax
if (tests.formdata) { var xhr = new XMLHttpRequest(); xhr.open('POST', 'upload.php'); xhr.onload = function() { progress.value = progress.innerHTML = 100; };
Un po’ vecchia maniera!. L’upload php si occuperà della gestione server side del file caricato, per esempio così:
$upload_dir=$_SERVER['DOCUMENT_ROOT'].'/upload/'; $filename=$_FILES['file']['name']; if(is_uploaded_file($_FILES['file']['tmp_name'])){ move_uploaded_file($_FILES['file']['tmp_name'],$upload_dir.$filename) or die($upload_dir.$filename); }
Il passaggio precedente si completa e integra con l’invio della chiamata e la sincronizzazione della progress bar:
if (tests.progress) { xhr.upload.onprogress = function (event) { if (event.lengthComputable) { var complete = (event.loaded / event.total * 100 | 0); progress.value = progress.innerHTML = complete; } } } xhr.send(formData); }
L’esempio
Una delle novità più interessanti di HTML5 che si sta diffondendo è il drag&drop, funzionalità non nuova al 100% perché già resa disponibile da librerie javascript come per esempio jQuery UI, ma che presenta interessanti novità oltre, ovviamente, ad essere disponibile nativamente nei browser. Già i browser, trattandosi di html5 si pone il solito problema dello stato del supporto, su caniuse possiamo farci un’idea rispetto alla nostra funzione.
Vediamo come funziona il nostro drag&drop.
Come prima cosa inseriamo nel nostro documento html un div che servirà da drop area e poi un’immagine da trascinare, questo elemento avrà l’attributo draggable=”true”:
<div id="box1" ondrop="drop(event)" ondragover="allowDrop(event)"></div> <img id="drag1" src="images/articolo.gif" draggable="true" ondragstart="drag(event)">
Oltre a quanto descritto è già stato inserito il codice javascript per la gestione degli eventi, il primo, ongragstart, si registra all’inizio del trascinamento; sul box invece abbiamo intercettato due eventi: ondragover, ovvero la sovrapposizione tra l’elemento trascinato e l’area di atterraggio e ondrop, ovvero il rilascio vero e proprio.
Vediamo cosa accade evento per evento partendo dal dragstart:
function drag(ev){ ev.dataTransfer.setData("Text",ev.target.id); }
Il metodo dataTransfer.setData () imposta il tipo di dati e il valore dei dati trascinati: nel nostro caso dentro Text l’id dell’immagine che trasciniamo.
L’evento onDragOver specifica dove i dati trascinati possono essere rilasciati.
function allowDrop(ev){ ev.preventDefault(); }
Per impostazione predefinita, i dati / elementi non possono essere rilasciati in altri elementi. Per consentire il drop quindi dobbiamo evitare la gestione predefinita.
Infine il drop vero e proprio:
function drop(ev){ ev.preventDefault(); var data=ev.dataTransfer.getData("Text"); ev.target.appendChild(document.getElementById(data)); }
Dove si va a leggere l’id dell’elemento in fase di trascinamento e con il metodo appendChild applicato al target, il nostro div, aggiungiamo la nostra immagine.
L’esempio
Seguendo il percorso iniziato con easel.js proviamo ad animare due oggetti in un canvas, uno avrà un FPS governato da una select, l’altro sarà sempre costante.
Quindi partendo dal solito html, aggiungiamo una funzione javascript che crea due oggetti timeCircle e tickCircle e poi procediamo con la solita animazione:
function init() { stage = new createjs.Stage("myCanvas"); timeCircle = new createjs.Shape(); timeCircle.graphics.beginFill("red").drawCircle(0, 0, 40); timeCircle.y = 50; stage.addChild(timeCircle); tickCircle = new createjs.Shape(); tickCircle.graphics.beginFill("blue").drawCircle(0, 0, 40); tickCircle.y = 150; stage.addChild(tickCircle); createjs.Ticker.addEventListener("tick", tick); createjs.Ticker.setFPS(20); }
Le varibili stage, timeCircle, tickCircle, dovranno esser globali.
A questo punto inseriamo nell’html la select per regolare la velocità del primo cerchio:
<select onchange="createjs.Ticker.setFPS(Number(this.value));"> <option value="10">10 fps</option> <option value="20" selected>20 fps</option> <option value="30">30 fps</option> <option value="40">40 fps</option> <option value="50">50 fps</option> <option value="60">60 fps</option> </select>
All’onchange modifichiamo il FPS. Resta da definire la funzione tick:
function tick(event) { // time based timeCircle.x = timeCircle.x + (event.delta)/1000*100; if (timeCircle.x > stage.canvas.width) { timeCircle.x = 0; } // not time based: tickCircle.x = tickCircle.x + 5; // 100 / 20 = 5 if (tickCircle.x > stage.canvas.width) { tickCircle.x = 0; } stage.update(); }
Dove il secondo è sempre costante, mentre la velocità del primo è calcolata a partire dal valore passato dalla select (o dalla chiamata iniziale)
Ecco l’esempio
Partendo dal post precedente proveremo ad animare il cerchio rosso.
A livello di html non cambia nulla, solo creeremo un canvas 500×100, sarà sufficiente.
La variazione principale consisterà nell’introduzione di questa funzione (banale):
function tick() { circle.x = circle.x + 5; if (circle.x > stage.canvas.width) { circle.x = 0; } stage.update(); }
che serve per spostare il centro del cerchio di 5px dando lil movimento, ovviamente alla fine dello stage riparte dall’inizio.
Notare e non dimenticare l’update dello stage
Ma la cosa interessante è come si attiva questa funzione, ovvero queste due righe di codice:
createjs.Ticker.addEventListener("tick", tick); createjs.Ticker.setFPS(30);
Il Ticker da una trasmissione a intervalli regolari e viene inviata una chiamata alla funzione tick, ad ogni “tick”.
L’ultima opzione consente di impostare la frequenza dei fotogrammi in fotogrammi al secondo (FPS). Ad esempio, con un intervallo di 40, getFPS () restituisce 25 (1000ms al secondo diviso per 40 ms per ogni tick = 25fps).
L’esempio
In questo post proveremo a lavorare con il canavs usando una libreria apposita: easel.js
La prima operazione sarà disegnare un semplice cerchio all’interno del canvas, lo scopo di questa libreria, ma di tutte le sue sorelle, è quello di rendere meno gravosa la programmazione tramite le API native di html5.
Tanto per cominciare partiremo con un semplicissimo html che conterrà, indovinate un po’, un canvas:
<body onLoad="init();"> <canvas id="myCanvas" width="500" height="300"></canvas> </body>
Già si vede che all’onload del body verrà chiamata la funzione che disegnerà e quindi passiamo al javascript,
come prima cosa colleghiamo la libreria:
<script src="js/easeljs-0.6.0.min.js"></script>
e quindi procediamo con lo script. Come prima cosa creiamo il nostro stage legandolo al canvas
e in seguito creiamo il nostro cerchio
var stage = new createjs.Stage("myCanvas"); var circle = new createjs.Shape();
successivamente usiamo il metodo drawCircle dell’oggetto graphics per disegnare il cerchio dando le coordinate del centro e il raggio,
usando la concatenabilità lo coloriamo di rosso:
circle.graphics.beginFill("#F00").drawCircle(0, 0, 50);
Gli ultimi due passaggi sono aggiungere l’elemento allo stage e aggiornare lo stesso:
stage.addChild(circle); stage.update();
Qui si trova la documentazione della libreria
Mentre questo è l’esempio
Terzo post dedicato a svgjs. Oggi ci occuperemo del binding degli eventi.
La base di partenza è il prmo post dedicato alla libreria:
<script src="js/svg.min.js"></script> <style> .box{ margin:10px; border:1px solid #000; float:left;} </style> </head> <body> <div id="box1"></div> <script> if (SVG.supported) { var draw1 = svg('box1').size(200, 200); var rect = draw1.rect(100, 100).attr({ fill: '#f06' }); } else {alert('SVG not supported');} </script>
Creati gli oggetti su cui operare assegniamo a una variabile fclick una funzione anonima che realizza una piccola animazione. Quindi, sulla scia, delle ultime versioni di jQuery, usiamo il metodo on per assegnare all’evento click la funzione fclick, quindi cliccando sul rettangolo verrà modificato il colore di riempimento.
var fclick = function() {rect.animate(2000).attr({ fill: '#ff0' });}; rect.on('click', fclick);
L’esempio
Approfondendo il discorso iniziato nel precedente post su svgjs, oggi proviam ad animare gli oggetti creati precedentemente tramite dei bottoni javascript. La libreria è abbastanza semplice, ma offre già un buon numero di metodi per interagire con i nostri oggetti.
Procediamo quindi con l’html del precedente post e la creazione degli elementi tramite la libreria:
<script src="js/svg.min.js"></script> <style> .box{ margin:10px; border:1px solid #000; float:left;} </style> ... <div id="box1"></div> <div id="box2"></div> <script> if (SVG.supported) { var draw1 = svg('box1').size(200, 200); var rect = draw1.rect(100, 100).attr({ fill: '#f06' }); var draw2 = svg('box2').size(200, 200); var circle = draw2.circle(100); } else {alert('SVG not supported');} </script>
Per interagire con i nostri due elementi inseriremo 3 bottoni html, all’intenro dei quali intercetteremo l’evento onClick, poi questo passaggio operativamente potrebbe essere fatto in mille modi più intelligenti.
<input type="button" value="Nascondi" onClick="nascondi()"> <input type="button" value="Mostra" onClick="mostra()"> <input type="button" value="Animazione" onClick="animazione()">
Per quanto riguarda le prime due funzioni il codice è banale: gli elementi sono già disponibili in quanto definiti come variabili globali, si tratterà di utilizzare i metodi show() e hide() rispettivamente per mostrare e nascondere gli elementi.
Decisamente più interessante è invece la terza funzione: il metodo animate() mi permette di animare i miei elementi, il parametro passato determina la durata dell’animazione in millisecondi, un eventuale second parametro permetterebbe di definire l’accellerazione. In entrambi i casi procediamo a una traslazione di 75px verso sinistra e verso il basso, contemporanemaente nel primo caso procediamo con una rotazione di 45° mentrenel secondo modifichiamo il colore di riempimento. Ancora una volta si noti la comodità della conatenabilità che rende il codice decisamente sintetico.
function nascondi(){ rect.hide(); circle.hide(); } function mostra(){ rect.show(); circle.show(); } function animazione(){ rect.animate(2000).move(75, 75).rotate(45); circle.animate(2000).move(75, 75).attr({ fill: '#f03' }); }
L’esempio