{"id":213,"date":"2011-11-13T13:34:17","date_gmt":"2011-11-13T12:34:17","guid":{"rendered":"https:\/\/alistapart.com\/it\/article\/textarea-espandibili-con-classe\/"},"modified":"2011-11-13T13:34:17","modified_gmt":"2011-11-13T12:34:17","slug":"textarea-espandibili-con-classe","status":"publish","type":"article","link":"https:\/\/alistapart.com\/it\/article\/textarea-espandibili-con-classe\/","title":{"rendered":"Textarea espandibili con classe"},"content":{"rendered":"<div class=\"paragrafo\">\n<p><img decoding=\"async\" src=\"http:\/\/alistapart.com\/it\/wp-content\/uploads\/sites\/2\/2011\/11\/n39a1web.jpg\" border=\"0\" align=\"left\" \/>Un&#8217;area di testo che si espande \u00e8 un campo di inserimento testo multi-riga che si estende in altezza fino a contenere tutto il suo contenuto. Questo elemento di UI si trova comunemente nelle applicazioni sia desktop sia mobile, come il campo di composizione degli SMS su iPhone. Si possono trovare degli esempi nel Web, anche su Facebook, dove \u00e8 molto usato. E&#8217; una buona scelta ovunque non sappiate esattamente quanto testo verr\u00e0 inserito dall&#8217;utente e vogliate mantenere il layout compatto. In quanto tale, \u00e8 specialmente utile nelle interfacce per smartphone.<\/p>\n<p>Nonostante l&#8217;ubiquit\u00e0 di questo controllo, non c&#8217;\u00e8 un modo per crearlo usando solo HTML e CSS. Mentre i normali elementi block-level (come un <code>div<\/code>, ad esempio) si espandono per adattarsi al proprio contenuto, l&#8217;umile <code>textarea<\/code> non lo fa, anche se gli assegnate lo stile <code>display:block<\/code>&#8211; Dal momento che questo \u00e8 l&#8217;unico modo per accettare un input multi-linea da parte dell&#8217;utente (oltre ad usare <a href=\"http:\/\/www.w3.org\/TR\/html5\/editing.html#contenteditable\">contenteditable<\/a>, un intero mondo di dolore in cui non mi avventurer\u00f2 oggi), serve un po&#8217; di JavaScript per ridimensionarlo come desiderato.<\/p>\n<p>Passando Internet al setaccio, potete trovare molti tentativi di creazione di text area espandibili, ma la maggior parte \u00e8 affetta dai seguenti problemi:<\/p>\n<ul>\n<li>L&#8217;altezza \u00e8 calcolata cercando di indovinare dove possa avvenire la chiusura basandosi sull&#8217;attributo <code>cols<\/code>.<\/li>\n<li>L&#8217;altezza \u00e8 ricalcolata e impostata solo su eventi <code>keyup<\/code> (ed eventualmente <code>cut<\/code>\/<code>paste<\/code>). Questo non funziona se la <code>textarea<\/code> ha una larghezza fluida e la finestra viene ridimensionata.<\/li>\n<li>L&#8217;altezza richiesta \u00e8 calcolata in base all&#8217;attributo <code>scrollHeight<\/code>, il quale non \u00e8 specificato in nessuna specifica del W3C (\u00e8 stato introdotto da IE) e pertanto non sorprende ci siano subdole differenze tra le varie implementazioni, che richiedono blocchi di codice inelegante e fragile per fare browser-sniffing.<\/li>\n<\/ul>\n<p>La soluzione migliore che io abbia visto utilizza un elemento <code>pre<\/code> separato, posizionato assolutamente fuori dallo schermo, con lo stesso stile della <code>textarea<\/code>, che chiameremo <em>mirror element<\/em>. Utilizzanto <code>setTimeout<\/code>, la <code>textarea<\/code> viene poi interrogata ogni 200 ms circa ed ogni volta che si trova un nuovo valore, il contenuto del mirror element viene aggiornato. Questo poi si ridimensiona automaticamente per adattarsi al suo contenuto (come fa un normale elemento block-level), dopodich\u00e9 potete estrarre la dimensione dalla propriet\u00e0 <code>offsetHeight<\/code> ed applicarla alla <code>textarea<\/code>.<\/p>\n<p>Questo metodo funziona, ma il &#8220;polling&#8221; \u00e8 inefficiente, specialmente se avete pi\u00f9 textarea. Peggio ancora, se supportate le textarea di larghezza flessibile dovete anche controllare che la larghezza della <code>textarea<\/code> non sia cambiata ogni volta che fate una &#8220;poll&#8221; (ed \u00e8 costoso leggere da <code>offsetWidth<\/code>). Pu\u00f2 essere difficile calcolare l&#8217;esatta larghezza della content-box nella <code>textarea<\/code>, perci\u00f2 di solito c&#8217;\u00e8 un \u201c<a href=\"http:\/\/en.wikipedia.org\/wiki\/Fudge_factor\">fudge factor<\/a>\u201d aggiunto all&#8217;altezza applicata alla <code>textarea<\/code>, proprio per essere sicuri che non sia un po&#8217; troppo corta, risultando in una box che sia poi un po&#8217; troppo grossa per il contenuto. Qui, vi mostrer\u00f2 una soluzione migliore per questo problema, ossia assegnare una dimensione alla <code>textarea<\/code> utilizzando solo il pi\u00f9 piccolo frammento di codice magico JavaScript insieme ad alcuni trucchi CSS.<\/p>\n<p>La tecnica \u00e8 un miglioramento del posizionamento fuori dallo schermo del mirror element. Il primo miglioramento che facciamo \u00e8 collegato al modo in cui rileviamo l&#8217;input. L&#8217;evento <code>change<\/code> non \u00e8 l&#8217;ideale perch\u00e9 si attiva solo quando la <code>textarea<\/code> perde il focus. L&#8217;evento <code>keyup<\/code> funziona il pi\u00f9 delle volte, ma si attiva anche su eventi in cui non \u00e8 stato fatto alcun vero cambiamento, come il cursore che si muove a destra e a sinistra e non si attiva su l&#8217;utente usa il mouse per fare copia\/incolla. Quello che vogliamo in realt\u00e0 \u00e8 un evento che semplicemente si attivi ogniqualvolta cambi il valore della <code>textarea<\/code>. Fortunatamente, un tale evento esiste ed \u00e8 incredibilmente utile, sebbene sia menzionato cos\u00ec raramente che sembrerebbe che molti non siano a conoscenza della sua esistenza. L&#8217;evento \u00e8 semplicemente chiamato <code>input<\/code> e lo si usa proprio come qualunque altro evento:<\/p>\n<pre><code>textarea.addEventListener('input', function (event) {<br \/>    \/* Code to handle event *\/<br \/>}, false );<\/code><\/pre>\n<p>Quindi, il nostro primo miglioramento consiste nel fermare il &#8220;polling&#8221; utilizzando <code>setTimeout<\/code> ed invece usare l&#8217;evento molto pi\u00f9 efficiente <code>input<\/code>. E&#8217; supportato da tutti i browser, perfino da Internet Explorer a partire dalla versione 9, sebbene ovviamente ci sia un bug in IE: non si attiva quando cancellate il testo, cos\u00ec l&#8217;area non si restringer\u00e0 fino a quando non aggiungerete di nuovo del testo. Se questo vi crea problemi, potete guardare l&#8217;evento <code>keyup<\/code> in IE per coprire la maggior parte dei casi.<\/p>\n<p>Per IE8 possiamo usare l&#8217;evento proprietario <code>onpropertychange<\/code>, anch&#8217;esso si attiva quando la propriet\u00e0 <code>value<\/code> cambia. Questo evento \u00e8 anche disponibile sulle versioni precedenti alla 8, ma probabilmente c&#8217;\u00e8 bisogno di un po&#8217; di piccoli trucchi CSS per far funzionare in generale la textarea espandibile. Lascio il farlo funzionare in IE6 e IE7 come esercizio ai lettori sfortunati abbastanza da dover offrire il supporto a questi browser antichi.<\/p>\n<p>Ora, alcuni di voi potrebbero aver notato che non stiamo pi\u00f9 facendo polling: la <code>textarea<\/code> non si ridimensiona se ha una larghezza fluida e la finestra viene ridimensionata. Questo ci porta al nostro prossimo miglioramento: facciamo in modo che il browser ridimensioni la <code>textarea<\/code> automaticamente. Ma, vi sento gridare, pensavo che avreste chiesto se ci\u00f2 fosse possibile. Beh, non proprio. Non potete farlo automaticamente solo con HTML e CSS, ma tutto quello che JS deve fare \u00e8 aggiornare l&#8217;elemento mirror con il valore della <code>textarea<\/code>. Non deve misurare o esplicitamente impostare l&#8217;altezza. Il trucco consiste nel posizionare la <code>textarea<\/code> in cima all&#8217;elemento mirror, entrambe all&#8217;interno di un <code>div<\/code> contenitore posizionato relativamente. La <code>textarea<\/code> \u00e8 posizionata assolutamente e ha una larghezza ed altezza pari al 100% per far s\u00ec che riempia il <code>div<\/code>. Il mirror \u00e8 posizionato staticamente cos\u00ec che si espanda per adattarsi al suo contenuto. Il <code>div<\/code> contenitore si espander\u00e0 poi per adattarsi all&#8217;altezza del mirror e questo a sua volta far\u00e0 in modo che la <code>textarea<\/code> posizionata assolutamente si ridimensioni per riempire il contenitore, rendendolo cos\u00ec dell&#8217;altezza giusta adatta al suo contenuto.<\/p>\n<\/div>\n<div class=\"paragrafo\">\n<h2>Spiegazione sufficiente, dacci il codice!<\/h2>\n<p>Ehi, frenate un attimo! Ci sto arrivando! E&#8217; davvero splendidamente semplice. Il markup \u00e8 cos\u00ec:<\/p>\n<pre><code>&lt;div class=\"expandingArea\"&gt;<br \/>  &lt;pre&gt;&lt;span&gt;&lt;\/span&gt;&lt;br&gt;&lt;\/pre&gt;<br \/>  &lt;textarea&gt;&lt;\/textarea&gt;<br \/>&lt;\/div&gt;<br \/><\/code><\/pre>\n<p>Il <code>pre<\/code> \u00e8 il nostro mirror. Abbiamo bisogno di un <code>br<\/code> alla fine di questo per assicurarci che ogni spazio bianco copiato dalla <code>textarea<\/code> venga reso correttamente dal browser e non sgranocchiato. L&#8217;elemento <code>span<\/code> \u00e8 perci\u00f2 quello che in realt\u00e0 andiamo ad aggiornare con il contenuto della <code>textarea<\/code>.<\/p>\n<p>Ora, il CSS. Per prima cosa, un piccolo reset (probabilmente l&#8217;avete gi\u00e0 scritto):<\/p>\n<pre><code>textarea, <br \/>pre {<br \/>  margin: 0;<br \/>  padding: 0;<br \/>  outline: 0;<br \/>  border: 0;<br \/>}<\/code><\/pre>\n<p>Gli elementi contenitori hanno la classe <code>expandingArea<\/code>. Potete qui definire qualunque border o inset box-shadow, ecc., che volete usare per dare stile alla vostra textarea. Ho appena aggiunto un semplice border da 1px solid grigio. Potete anche impostare una propriet\u00e0 min-height se volete e funzioner\u00e0 come vi aspettate:<\/p>\n<pre><code>.expandingArea {<br \/>  position: relative;<br \/>  border: 1px solid #888;<br \/>  background: #fff;<br \/>}<\/code><\/pre>\n<p>Potete impostare qualunque padding, line height e stile di font desideriate, assicuratevi solamente che siano gli stessi per la <code>textarea<\/code> e per l&#8217;elemento <code>pre<\/code>.<\/p>\n<pre>.expandingArea &gt; textarea,<br \/>.expandingArea &gt; pre {<br \/>  padding: 5px;<br \/>  background: transparent;<br \/>  font: 400 13px\/16px helvetica, arial, sans-serif;<br \/>  \/* Make the text soft-wrap *\/<br \/>  white-space: pre-wrap;<br \/>  word-wrap: break-word;<br \/>}<\/pre>\n<pre><code>.expandingArea &gt; textarea {<br \/>  \/* The border-box box model is used to allow<br \/>   * padding whilst still keeping the overall width<br \/>   * at exactly that of the containing element.<br \/>   *\/<br \/>  -webkit-box-sizing: border-box;<br \/>     -moz-box-sizing: border-box;<br \/>      -ms-box-sizing: border-box;<br \/>          box-sizing: border-box;<br \/>  width: 100%;<br \/>  \/* This height is used when JS is disabled *\/<br \/>  height: 100px;<br \/>}<\/code><\/pre>\n<pre><code>.expandingArea.active &gt; textarea {<br \/>  \/* Hide any scrollbars *\/<br \/>  overflow: hidden;<br \/>  position: absolute;<br \/>  top: 0;<br \/>  left: 0;<br \/>  height: 100%;<br \/>  \/* Remove WebKit user-resize widget *\/<br \/>  resize: none;<br \/>}<\/code><\/pre>\n<pre><code>.expandingArea &gt; pre {<br \/>  display: none;<br \/>}<br \/>.expandingArea.active &gt; pre {<br \/>  display: block;<br \/>  \/* Hide the text; just using it for sizing *\/<br \/>  visibility: hidden;<br \/>}<br \/><\/code><\/pre>\n<p>Da ultimo, usiamo il seguente JavaScript. Per brevit\u00e0 ho omesso la solita feature detection che dovreste fare prima di usare <code>querySelector()<\/code> o <code>querySelectorAll()<\/code>. Nella miglior tradizione della graceful degradation, gli utenti con JavaScript disabilitato avranno una <code>textarea<\/code> di altezza fissa (con l&#8217;altezza impostata nel CSS), con delle scrollbar che appaiono quando il contenuto eccede:<\/p>\n<pre><code>function makeExpandingArea(container) {<br \/> var area = container.querySelector('textarea');<br \/> var span = container.querySelector('span');<br \/> if (area.addEventListener) {<br \/>   area.addEventListener('input', function() {<br \/>     span.textContent = area.value;<br \/>   }, false);<br \/>   span.textContent = area.value;<br \/> } else if (area.attachEvent) {<br \/>   \/\/ IE8 compatibility<br \/>   area.attachEvent('onpropertychange', function() {<br \/>     span.innerText = area.value;<br \/>   });<br \/>   span.innerText = area.value;<br \/> }<br \/> \/\/ Enable extra CSS<br \/> container.className += ' active';<br \/>}<br \/><br \/>var areas = document.querySelectorAll('.expandingArea');<br \/>var l = areas.length;<br \/><br \/>while (l--) {<br \/> makeExpandingArea(areas[l]);<br \/>}<br \/><\/code><\/pre>\n<p>Una nota sulla delegation: potete facilmente impostarla con un singolo event listener nel <code>document node<\/code> ed usate l&#8217;event delegation per gestire efficientemente pi\u00f9 text area espandibili, ma solo se non dovete supportare IE8 o inferiori, perch\u00e9 Microsoft, nella sua infinita saggezza, non ha implementato l&#8217;evento <code>onpropertychange<\/code>.<\/p>\n<\/div>\n<div class=\"paragrafo\">\n<h2>La demo obbligatoria<\/h2>\n<div class=\"expandingArea active\">\n<pre><span><\/span><br \/><\/pre>\n<p><textarea cols=\"10\" rows=\"4\">Guarda come mi espando per contenere tutte le tue parole!<\/textarea><\/div>\n<p><script type=\"text\/javascript\">\/\/ &lt;![CDATA[\nfunction makeExpandingArea(container) {\n var area = container.querySelector('textarea');\n var span = container.querySelector('span');\n if (area.addEventListener) {\n   area.addEventListener('input', function() {\n     span.textContent = area.value;\n   }, false);\n   span.textContent = area.value;\n } else if (area.attachEvent) {\n   \/\/ IE8 compatibility\n   area.attachEvent('onpropertychange', function() {\n     span.innerText = area.value;\n   });\n   span.innerText = area.value;\n }\n \/\/ Enable extra CSS\n container.className += ' active';\n}<\/p>\n<p>var areas = document.querySelectorAll('.expandingArea');\nvar l = areas.length;<\/p>\n<p>while (l--) {\n makeExpandingArea(areas[l]);\n}\n\/\/ ]]&gt;<\/script><\/p>\n<\/div>\n<div class=\"paragrafo\">\n<h2>Note conclusive<\/h2>\n<p>A causa del modo in cui l&#8217;aggioramento della grafica viene effettuato in Opera per Mac OS X, si pu\u00f2 verificare un leggero sfarfallio quando viene aggiunta una nuova riga al text field. Potete aggirare questo problema rendendolo sempre una riga pi\u00f9 alta di quello che \u00e8 necessario in Opera su Mac. Aggiungete semplicemente questo codice in cima alla funzione <code>makeExpandingArea<\/code> (tristemente non c&#8217;\u00e8 modo di fare feature detection per questo):<\/p>\n<pre><code>if ( window.opera &amp;&amp; \/Mac OS X\/.test( navigator.appVersion ) ) {<br \/>  container.querySelector( 'pre' )<br \/>           .appendChild(<br \/>    document.createElement( 'br' )<br \/>  );<br \/>}<br \/><\/code><\/pre>\n<p>Da ultimo, poich\u00e9 la <code>textarea<\/code> \u00e8 posizionata direttamente sopra a <code>pre<\/code>, potete estenderla per fare delle cose fuori dall&#8217;ordinario come sottolineare la sintassi mentre scrivete. Se fate il parsing del valore e lo dividete in diversi tag prima di aggiungerlo al <code>pre<\/code>, potete applicare diversi colori a diverse sezioni. Dovrete rimuovere la dichiarazione <code>visibility: hidden<\/code> da <code>pre<\/code> e invece aggiungere <code>color: transparent<\/code> a <code>textarea<\/code>. Usiamo questa tecnica in <a href=\"https:\/\/mail.opera.com\">My Opera Mail<\/a> per rendere pi\u00f9 semplice scorrere i nomi nei campi A\/CC\/CCn dello schermo di composizione. L&#8217;insidia nascosta \u00e8 che tutti i browser tranne Opera rendono il colore del cursore uguale al colore del testo, cos\u00ec il cursore sparisce quando si rende il colore trasparente. Non credo che ci sia alcuno standard W3C che copra questo argomento (per favore, fatemi sapere se ho torto), ma la piattaforma standard (basata sui text editor &#8220;che hanno a bordo&#8221;) sembra essere l&#8217;opposto del colore di background in Windows e sempre nera in Mac, indipendentemente dal color del background o del foreground. Ma fino a che gli altri browser non vedranno la luce e sistemeranno questo comportamento potrete ancora applicare la sottolineatura della sintassi su blur e spegnerla mentre l&#8217;utente sta in effetti facendo editing o cambiando al contrario il colore di background.<\/p>\n<p>E questo \u00e8 tutto amici! Spero vi sia piaciuto leggere questo articolo e che magari abbiate imparato una tecnica utile. E&#8217; elegante ed efficiente e funziona bene tanto nei browser mobile moderni cos\u00ec come in quelli desktop. Felice hacking!<\/p>\n<p>Illustrazioni: {carlok}<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Un&#8217;area di testo espandibile \u00e8 un input field di testo multi-linea che si espande in altezza per includere tutto il suo contenuto. Si trova comunemente nelle applicazione desktop e mobile, come il campo di composizione degli SMS negli iPhone. Costituisce una buona scelta quando non si sa quanto testo scriver\u00e0 l&#8217;utente e voi desiderate fare in modo che il layout sia compatto. In quanto tale, \u00e8 specialmente utile sulle interfacce pensate per gli smartphone. Tuttavia, nonostante l&#8217;ubiquit\u00e0 di questo controllo, non c&#8217;\u00e8 un modo per crearle utilizzando solo HTML e CSS e la maggior parte delle soluzioni JavaScript soffre di supposizioni, inaccuratezza o una certa mancanza di eleganza&#8230; fino ad ora!<\/p>\n","protected":false},"author":818,"featured_media":7000636,"comment_status":"open","ping_status":"open","template":"","categories":[247,279,271,53],"tags":[],"coauthors":[344],"class_list":["post-213","article","type-article","status-publish","has-post-thumbnail","hentry","category-html","category-interaction-design","category-javascript","category-numero-39-15-novembre-2011"],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/alistapart.com\/it\/wp-json\/wp\/v2\/article\/213","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/alistapart.com\/it\/wp-json\/wp\/v2\/article"}],"about":[{"href":"https:\/\/alistapart.com\/it\/wp-json\/wp\/v2\/types\/article"}],"author":[{"embeddable":true,"href":"https:\/\/alistapart.com\/it\/wp-json\/wp\/v2\/users\/818"}],"replies":[{"embeddable":true,"href":"https:\/\/alistapart.com\/it\/wp-json\/wp\/v2\/comments?post=213"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/alistapart.com\/it\/wp-json\/wp\/v2\/media\/7000636"}],"wp:attachment":[{"href":"https:\/\/alistapart.com\/it\/wp-json\/wp\/v2\/media?parent=213"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/alistapart.com\/it\/wp-json\/wp\/v2\/categories?post=213"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/alistapart.com\/it\/wp-json\/wp\/v2\/tags?post=213"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/alistapart.com\/it\/wp-json\/wp\/v2\/coauthors?post=213"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}