spájame
slovenskú
IT komunitu
pridaj sa
Registrácia · Login

František Čaník 12.12.2012
Hodnoť článok:
15 0

Ako na to: Cropovanie obrázku okamžite po uploade?

Stáva sa Vám často, že potrebujete aby používateľ okamžite ako nahrá obrázok, upravil jeho rozmery na Vami požadovaný formát? Zdá sa Vám, že ste nenašli dostatok pekných príkladov? Skúste si to s nami. Náročnosť: mierne pokročilý

Aby sa nepovedalo, že toto nie je autorský článok, tak rovno napíšem, že áno, pluginy som nevyrábal, len som ich skopíroval z githubov. Funkcionalitu som však vymýšľal sám, nakoľko som to riešil na konkrétnom klientovi, takže ide o reálnu vec, ktorá je reálne nasadená v prevádzke a musím sa pochváliť, že bola v celku úspešná. Aj to je dôvod, prečo ju sem dávam. V závere príspevku nájdete odkaz na DEMO a ZDROJOVÉ SÚBORY.

Čo budeme potrebovať?

Ako ste si možno všimli, celé je to postavené v podstate na kombinácii 2 rôznych pluginov, ktorých základ spočíva v jQuery, takže si budeme musieť stiahnuť nasledovné súbory:

  • jQuery 1.7.2 minified
    Klasický framework, ktorý asi vôbec netreba predstavovať a určite to nemám ani v úmysle, pretože tento článok je skôr pre takých tých "naskillovaných"...
  • FileUploader by Valums v. 2.0
    Knižnica, umožňujúca jednoduchý i viacnásobný upload pomocou odosielania dát v podobe streamu, čo umožňuje odsledovanie celkového i partiálneho progressu (v prípade multiuploadu)
  • jCrop by Deep Liquid
    Veľmi pekný a príjemný cropper, umožňujúci množstvo nastavení a prispôsobujúci napríklad "neposlušné" a zbytočne veľké obrázky do celkom "znesiteľného" pomeru.

Začíname, alebo čo by sme mali vedieť?

V prvom rade je viac ako jasné, že budeme potrebovať načítať všetky potrebné veci do súboru, takže si do hlavičiek šupneme všetky potrebné JS a CSS súbory. Obsah, ktorý si vložíme do body je nasledovný:

<div id="container">
    <div id="upload"></div>
    <div id="preview-container"></div>
    <form id="croper" method="post" action="crop.php">
         <input type="hidden" id="x" name="x" />
         <input type="hidden" id="y" name="y" />
         <input type="hidden" id="w" name="w" />
         <input type="hidden" id="h" name="h" />
         <input type="hidden" id="src" name="src" value="" />
    </form>
</div>

V kóde, ktorý máte možnosť vidieť vyššie sú uvedené prvky, ktoré nám pomôžu pri cropovaní. V prvom rade si všimnite, že tam nikde nie je input type="file". Ako teda nahrávať súbor a ešte k tomu bez tohto prvku? Nehovoriac o tom, že formulár taktiež neinformuje o tom, že ide o multipart/form-data. Prečo je tomu tak? Odpoveď je jednoduchá - nahrávanie budeme riešiť cez plugin - qqFileuploader.

Pripravíme si základ uploader-u

Aby sa nám dokázal uploader inicializovať, musíme si ho najskôr napísať. Základná verzia, ktorá vygeneruje úplne štandardný fileuploader by mala vyzerať nasledovne:

<script type="text/javascript">

function createUploader() {
var uploader = new qq.FileUploader({
	element: document.getElementById('upload'),
        action: 'upload.php',
	autoUpload: true,
	allowedExtensions: ['JPG', 'jpg','jpeg','JPEG','png'],
	multiple: false,
	uploadButtonText: 'Zvoliť súbor'
    });
}
window.onload = createUploader;
</script>

Nasledovný kúsok kódu Vám zmení div#upload na plne funkčný uploader spolu s tzv. dropzónou. Výsledok vygenerovaného kódu je nasledovný:

<div class="qq-uploader">
   <div class="qq-upload-drop-area" style="display: none;">
      <span>Drop files here to upload</span>
   </div>
   <div class="qq-upload-button" style="position: relative; overflow: hidden; direction: ltr;">Zvoliť súbor<input type="file" name="file" style="position: absolute; right: 0px; top: 0px; font-family: Arial; font-size: 118px; margin: 0px; padding: 0px; cursor: pointer; opacity: 0;">
   </div>
      <ul class="qq-upload-list"></ul>
   </div>

Ako máte možnosť vidieť, zrazu tam máme hajdnutý input[type="file"]. Na daný element a jeho zmenu je nabindovaných niekoľko akcií, ktoré sa vykonajú pri kliku ako aj pri zmene. Tu sa však ešte ani zďaleka neodstávame k tomu, čo ste mohli vidieť na obrázku a teda k peknému progress bar-u s percentami a infoškou o tom, že sa ešte len niečo nahráva a treba si počkať. Ako sa k tomu však dopracujeme?

Eventy uploaderu
Aby sme mali kontrolu nad celým procesom, predstavme si niekoľko dôležitých eventhandlerov, ktoré uploader obsahuje.

  • onSubmit (id, fileName)
    callback zavolaný okamžite po tom, ako je vybraný súbor a je teda spustené nahrávanie na server. Pomocou tohto callbacku si pridáme informáciu o tom, že sa začalo nahrávanie súboru.
  • onProgress (id, fileName, uploadedBytes, totalBytes)
    callback volaný počas nahrávania v určitých intervaloch (len ak je použité ajaxové nahrávanie). Pomocou tohto callbacku si budeme meniť veľkosť progress bar-u a zobrazovať percentuálne stav uploadu.
  • onComplete (id, fileName, responseJSON)
    Callback zavolaný keď je súbor nahraný na server. Zavolá sa v prípade, že upload prešiel kompletne bez chýb, v inom prípade je volaný onError callback. Tento si však necháme na to, aby sme zobrazili nahraný obrázok a inicializovali cropovanie.
  • onError (id, fileName, errorReason)
    Callback zavolaný v prípade, že niečo počas nahrávania zlyhalo.

Rozšírme si teda náš javascript, ktorý vytvára náš uploader.
Pridáme si onSubmit

function createUploader() {
var uploader = new qq.FileUploader({
	element: document.getElementById('upload'),
        action: 'upload.php',
	autoUpload: true,
	allowedExtensions: ['JPG', 'jpg','jpeg','JPEG','png'],
	multiple: false,
	uploadButtonText: 'Zvoliť súbor',
        onSubmit:function(id, fileName) {
            $('#preview-container').html('<div id="smallProgress"></div><div id="smallBar"></div>Moment prosím, prebieha nahrávanie...<br/><small>'+fileName+'</small>').addClass('hold');
         }
    });
}

Ak sa vyznáte v jQuery, tak ste asi pochopili, že do div#preview-container pridáme div pre malý progress bar a informáciu o tom, že začalo nahrávanie + pripojíme názov zvoleného - nahrávaného súboru. Aby sme však ten progress bar vedeli aj nejako "oživiť", musíme si pridať aj ďalší callback - onProgress. Mimo to sme si k tomu pridali ešte jednu class-u - hold. Ide o CSS-ko, ktoré nám pridá do pozadia animáciu loader-u, no to si pozrieme ku koncu.

Callback onProgress

function createUploader() {
var uploader = new qq.FileUploader({
	element: document.getElementById('upload'),
        action: 'upload.php',
	autoUpload: true,
	allowedExtensions: ['JPG', 'jpg','jpeg','JPEG','png'],
	multiple: false,
	uploadButtonText: 'Zvoliť súbor',
        onSubmit:function(id, fileName) {
            $('#preview-container').html('<div id="smallProgress"></div><div id="smallBar"></div>Moment prosím, prebieha nahrávanie...<br/><small>'+fileName+'</small>').addClass('hold');
         },
        onProgress: function(id, fileName, uploadedBytes, totalBytes)
        {
    	$('#smallProgress').html( Math.round(uploadedBytes / totalBytes * 100) + '%');
    	$('#smallBar').css('width',Math.round(uploadedBytes / totalBytes * 100) + '%');
    }
});
}

Keďže už vieme, kedy sme dorazili na 100%, je fajn vedieť ešte aj to, či to bolo alebo nebolo ok. Ak všetko prešlo úspešne, mali by sme si zobraziť obrázok a následne umožniť jeho orezanie. Tu nám pomôže callback onComplete.

function createUploader() {
var uploader = new qq.FileUploader({
	element: document.getElementById('upload'),
        action: 'upload.php',
	autoUpload: true,
	allowedExtensions: ['JPG', 'jpg','jpeg','JPEG','png'],
	multiple: false,
	uploadButtonText: 'Zvoliť súbor',
        onSubmit:function(id, fileName) {
            $('#preview-container').html('<div id="smallProgress"></div><div id="smallBar"></div>Moment prosím, prebieha nahrávanie...<br/><small>'+fileName+'</small>').addClass('hold');
         },
        onProgress: function(id, fileName, uploadedBytes, totalBytes)
        {
    	$('#smallProgress').html( Math.round(uploadedBytes / totalBytes * 100) + '%');
    	$('#smallBar').css('width',Math.round(uploadedBytes / totalBytes * 100) + '%');
    },
onComplete: function(id, fileName, responseJSON) {
        $('#preview-container').removeClass('hold');
        $('#preview-container').html('<img style="visibility:hidden!important;min-widht: 530px;min-height: 195px;" id="toCrop" src="uploads/'+responseJSON.file+'" />');
        $('#src').val(responseJSON.file);
        $('#preview-container').prepend('<div class="hold" id="tempInfo">Moment, čakáme na zobrazenie obrázku pre orezávanie.</div>');
        $('#toCrop').load(function(){
        	$('#tempInfo').remove();
			$('#toCrop').Jcrop({
		        bgOpacity: 0.5,
		        aspectRatio : 134/87,
		        bgColor: 'black',
		        addClass: 'jcrop-dark',
		        minSize: [300, 195],
		        boxWidth: 530,
		        onSelect: updateCoords,
		        onChange: updateCoords,
		        onDblClick: function(e) {
   	        	     $('#croper').submit();
		        }
		      },function(){
		        api = this;
		        api.animateTo([50,50,50+300,50+195]);
		        api.setOptions({ bgFade: true });
		        api.ui.selection.addClass('jcrop-selection');
		      });

                    $('#toCrop').css('visibility','visible');
        });
        },
    });

Samotný onComplete callback obsahuje už aj funkciu, ktorá oživí jCrop a tak si poďme povedať, čo presne sa stane v tomto kroku.

  1. Zruší sa .hold
  2. Obsah #preview-container zmeníme za img#toCrop, ktorý však má príznak, že nie je viditeľný čo sa týka visibility. Ak by sme dali display:none cropper by nedokázal rozoznať presné rozmery a tým pádom by došlo ku chybe pri inicializácii cropperu
  3. pred #preview-container doplníme tzv. temporary informáciu - div#tempInfo, ktorý obsahuje textovú infošku o tom, že sa pripravuje crop. Táto vec je tam hlavne preto, aby sme zbytočne nezobrazovali obrázok, ktorý sa "doťahuje" niekde na pozadí.
  4. inicializujeme cropper avšak až po tom, čo sa úspešne načíta celý obrázok - $('#toCrop').load(function(){ ... })

Tu už potom máme plne funkčnú verziu uploaderu s inicializáciou cropperu. Dôležité sú však ešte súbory, ktoré riadia upload fileuploader.php = php trieda a samotný crop.php, ktorý sa stará o orezanie.

  • upload.php php
    Súbor, ktorý v sebe načítava triedu pre fileuploader, aby vedel spracovávať ajaxové XHR requesty a následne vykladávať súbor a teda príjmať rôzne dáta.
  • fileuploader.php php trieda
    Samostatná trieda, ktorá obsahuje všetky potrebné funkcie, ktoré handlujú requesty odosielané na upload.
  • crop.php php
    Skript, ktorý na základe koordinátov vytvorí z originálneho obrázku kópu, ktorý následne resampluje a oreže z pozície X a Y na danú W(šírku) a H(výšku).


Naštýlovanie

Vzhľadom na štruktúru, ktorú vytvára samotný fileuploader, je možné že sa Vám stane to, že budete mať veci duplicitne. Nebojte sa, nič sa nedeje - máme predsa CSS, nie?

Pri odosielaní súboru sa vytvorí v qq-upload-list nový LI element, ktorý obsahuje hneď niekoľko prvkov, ktoré sú v tomto našom príklade úplne nežiadúce a teda ich radšej "zašijeme". Náš štýl by preto určite musel obsahovať triedu .hold, ktorú využívame pre uploader a zároveň prepíšeme triedy, ktoré zobrazujú defaultné prvky.

Cropnutie obrázku

Možno ste si to v tom kóde všimli a možno nie, ale je tam funkcia, ktorá pri zmene a výbere aktualizuje koordináty v hidden poliach. Samozrejmosťou je aj pridanie funkcie onDblClick, ktorá znamená, že celý CROP sa odohrá ak použijete dvojklik v políčku, ktoré máte označené. To jest - doubleclick v cropovanom obsahu.

/* skryjeme to, čo sme si vytvorili a prispôsobili */

.qq-upload-list,
.qq-upload-file {
	display:none;
}

/* prispôsobenie loadingu */

.hold {
	position:relative;
	background: url(../img/loading.gif) 30px 51% no-repeat;
	padding: 60px 0 60px 130px;
	font-size: 14px;
	text-align: left;
	vertical-align: central;

}

.hold small {
	font-size: 11px;
	color: #01BDED;
}

.hold #smallBar {
	position: relative;
	bottom: 10px;
	width:0;
	height:4px;
	background: #01BDED;
	-webkit-border-radius: 4px;
	-moz-border-radius: 4px;
	border-radius: 4px;
	height: 4px;				

}

.hold #smallProgress {
	position: absolute;
	width: 60px;
	text-align:right;
	top: 40%;
	left: 65px;
	font-size: 30px;
}

Samozrejmosťou je ešte CSS pre samotný cropper, no to si už pozrite v príkladoch, resp. v DEMO kóde. Všetko si môžete aj stiahnuť :). V prípade, že by ste mali nejaké tie otázočky, tak využite kľudne komentáre.

Záverom?

Napísanie článočku trvalo niečo okolo 2 hodiniek, pretože som to musel kopčiť, skúšať a pripravovať. Uploader sa dá samozrejme rôzne upravovať, no kým by som napísal všetky možnosti, tak asi by mi padla hlava na klávesnicu a už by ste z toho mali guláš. Verím, že sa Vám tento článoček bude páčiť a som zvedavý, či ho niekto nájde v takejto forme v anglickom alebo inom jazyku, nakoľko je to čisto moje "dielo" :). I hate haters :D smiech... Tak pekný deň a veľa úspechov pri skúšaní a prípadnej implementácii do Vašich projektov.

Zdroje a ukážka

DEMO ZDROJE

 

A ako to vyzerá?

František Čaník František Čaník

Programátor, webdesigner a webdeveloper niekoľkých webových projektov. Má za sebou prácu v reklamnej agentúre, médiách a na rôznych pozíciách v oblasti IT.


Hodnoť článok:
15 0

16 komentárov k článku:

Komentovať môžu iba prihlásení

Zaregistruj sa cez bezplatnú registráciu alebo použi login cez Facebook (FB Connect)

Prihlás sa tu, ak už máš profil na Zajtra.sk:


Zabudol som heslo

0 0 František Čaník 4.1.2013 17:34:47
Mno... Pre mňa je externé len to, čo nesúvisí s mojím serverom => všetko čo sa doťahuje z inej lokality ako mojej. V opačnom prípade sa nehovorí o externom, ale skôr o cudzom kuse kodu, co je - ako isto uznas - opat nieco uplne ine. Inymi slovami a zjednodusenie, hovorime potom o kode tretich stran, alebo nie?
0 0 Erik Márföldi 4.1.2013 15:19:38
Pozor na vyjadrovanie sa :) Externý JavaScript je aj lokálny. Ak nepíšeme JS priamo do html súboru, tak ich referencujeme na externý súbor a je jedno či je uloženy lokálne alebo hostovaný na inom serveri.
0 0 František Čaník 4.1.2013 14:19:47
@Peter Širka mno. všetko záleží od pohľadu. jQuery nemusíš mať externe, môžeš ho mať aj lokálne a tak pod. Proste všetko je vec vkusu používania a usability. Pamätám si na časy, kedy proste Facebook-ovské pluginy brzdili načítavanie stránok, ale to si myslím, že je už tiež dávno tě tam.
0 0 Peter Širka 4.1.2013 11:26:02
Ja som divný, ja by som si scripty napísal sám ... Keď sa pozerám na dnešné stránky - áno sú funkčné, sú pekné, ale zdrojový kód ... 20 externých scriptov (tie externé script volajú ďalšie externé scripty) a potom stránka ide pomaly, vlastne niekedy ani nejde a hľadať chybu je ako hľadať ihlu v kope sena.

Nemám nič proti článku, len som možno niekedy staromódny...
0 0 František Čaník 4.1.2013 05:24:59
No čo ľudkovia? Pripravíme prvé rozšírenie, ktoré by kontrolovalo pomer strán, určovalo niečo ako "kvalitu" a zobrazilo náhľady nahraných súborov, pričom by umožnilo napríklad rôzne ďalšie eventy typu "edit, crop, delete"?
0 0 Erik Márföldi 12.12.2012 19:27:53
@František Čaník Ja som nenarážal na teba :) Pridal som to ako tip pre ostatných :)
0 0 František Čaník 12.12.2012 18:54:38
@Erik Márföldi veď toto, veď toto... :) a úprimne, o 4-tej ráno moc nepremýšlaš nad reťazením v jquery, ale áno, dá sa v prípade preview-containeru ušetriť zopár znakov :)
0 0 Erik Márföldi 12.12.2012 18:07:37
@František Čaník Oh, typo :) Samozrejme bez prvých dvoch bodkočiarek.
1 0 František Čaník 12.12.2012 17:42:31
@Erik Márföldi len by si tam nemohol mať tie ; ... ale inak poho
0 1 Erik Márföldi 12.12.2012 16:12:35
Možno niekto vie, možno nie. jQuery kód môžeme aj reťaziť. Ušetríme tým zopár znakov :)

$('#preview-container')
.removeClass('...');
.html('...');
.prepend('...');

Palec hore.
0 0 Andrej Mihaliak 12.12.2012 11:58:24
super článok !
0 0 František Čaník 12.12.2012 11:20:08
@Mikeak si pozrieš zdojáky, uvidíš, že všetko čo sa nahrá sa ukladá dvojmo s tým, že jeden súbor je prefixovaný ako original_, z ktorého sa vždy vychádza pri orezávaní a druhý je bez prefixu, ktorý je vo veľkosti výsledného rozmeru, ktorý je v súbore crop.php určený parametrami $targ_w a $targ_h.
1 0 Milan Dvorský 12.12.2012 10:30:19
dikes!
0 0 Mike 12.12.2012 10:28:14
jCrop pouzivam uz dlho, ale vlastnu class. Tu este chyba nech obrazok resizne a treba FIXNY picker na ten crop napr. 200x150 (nech robi rovno thumby napr. do galerie)
3 0 František Čaník 12.12.2012 10:22:51
@Martin Litvaj aj tento uploader zvláda multiuploading, pripravím ešte článok ako generovať napríklad nahrávanie obrázkov do galérie s progressom a malými náhľadmi, takže sa je na čo tešiť :)
0 0 Martin Litvaj 12.12.2012 09:42:36
osobne tento clanok pravdepodobne v blizkej buducnosti vyuzijem. cropovanie som doteraz nemusel riesit, tak si mi usetril cas.

ktory jquery multi img uploader by si odporucil? pri jednom projekte som stavil na uploadify.com ale su s nim len problemy.
Zajtra.sk > Programovanie > JS/jQuery > Ako na to: Cropovanie obrázku okamžite po uploade?


Kritika

Vieš ako robiť veci lepšie? Pomôž našim odvážnejším členom a skritizuj im projekty!

Reklama

Seriály zo Zajtra.sk

Reklama