Canvas color picker

Jakiś czas temu stworzyłem stronkę ułatwiającą używanie ikon bootstrapaarrow-up-right. Chciałem tam zaimplementować color picker, ale okazało się, że większość sensownych działa w oparciu o jQuery, a jako że traktowałem ten projekt jako wyzwanie, nie chciałem iść na skróty. Postanowiłem więc w ramach treningu stworzyć coś własnego.

Poniższy tekst nie należy do tych prostszych. Nie prowadzę w nim za rączkę, opisując tylko przykładowy proces działania. Dodatkowo zakładam, że wiele rzeczy z tego kursu już przerobiłeś, ponieważ nie będę dokładnie ich opisywał.

Zaczynamy.

Po przejrzeniu kilku rozwiązań wymyśliłem sobie, że końcowy wygląd color pickera będzie wyglądał tak jak poniżej:

Czyli bez kombinowania i udziwniania.

Stworzyłem więc przykładowy html i css:

Pokaż HTMLarrow-up-right Pokaż CSSarrow-up-right

Całość oczywiście będziemy tworzyć dynamicznie z podziałem na oddzielne pliki. Nie będziemy tutaj natomiast używał narzędzi bundlujących - ale jeżeli chcesz, śmiało je dodaj analogicznie do innych projektów.

Funkcje pomocnicze

Z pewnością będziemy potrzebować kilku funkcji do konwersji kolorów między sobą - ot chociażby gdy podamy kolor w notacji heksadecymalnej i będziemy chcieli pobrać z niej kolor czerwony (skonwertujemy ją do rgb). Przejrzałem kilka wątków na stackoverflow oraz kodów różnych pluginów i uzyskałem mniej więcej taki zbiorek jak poniżej.

Klasa color picker

Zaczynam od głównej klasy, która zepnie wszystko w całość. Tworzymy klasę, do której tym razem przekażę miejsce umieszczenia pickera.

Hue slider

Pierwszym elementem który utworzymy będzie górny suwak, który będzie służył do wybierania odcienia.

Funkcja createElement() utworzy tło suwaka jako canvas, oraz wskaźnik, który będziemy mogli przesuwać wybierając aktualną barwę:

Kolejna funkcja setBgGradient() posłuży nam do stworzenia gradientowego tła:

Pozostaje więc podłączyć przesuwanie wskaźnika na sliderze.

Dwa zdarzenia (mousemove i mouseup) podpiąłem dla całego dokumentu. Jeżeli wszystko podpiął bym dla canvasu, użytkownik przesuwając wskaźnik musiał by idealnie prowadzić swój kursor, co by było bardzo uciążliwe. Dzięki powyższemu rozwiązaniu użytkownik kliknie na slider, zacznie przesuwać, ale spokojnie będzie mógł uciec kursorem poza obszar slidera.

Podczas przesuwania kursora myszy będę ustawiał wskaźnik w odpowiedniej pozycji. Żeby to zrobić muszę go ustawić w pozycji e.pageX i e.pageY, ale równocześnie w przedziale od 0 do szerokości canvasu. Mógł bym to zrobić kilkoma prostymi ifami, ale użyję do tego funkcji clamp() (1arrow-up-right), którą zaimportowałem z pliku z funkcjami.

Wskaźnik ustawiam maksymalnie w pozycji this.canvas.width - 1. W testach wyszło mi, że pozycja this.canvas.width zwracała czarny kolor.

Na koniec potrzeba mi jeszcze 2 funkcji - jednej do ustawiania odpowiedniego koloru (czyli też ustawianiu wskaźnika w odpowiednim miejscu), oraz pobierającej aktualny kolor:

Komunikacja między komponentami

Gdy ustawię nowy kolor dla tego slidera, chciałbym by reszta elementów w naszym pikerze na to zareagowała. Przykładowo gdy użytkownik przesunie wskaźnik na czerwoną barwę, główna plansza do wyboru koloru powinna zmienić całą swoją kolorystykę. Wykorzystam do tego opisany w tym rozdziale mechanizm sygnałów.

Wpierw tworzę dodatkowy plik z prostą klasą tworzącą obiekty typu obserwer:

A następnie importuję ją i podpinam pod powyższy suwak tworząc sygnał:

Testowanie

Żeby sprawdzić czy wszystko działa jak należy, wracam do głównej klasy, tworzę jeden element na bazie klasy HueSlider i podpinam się pod jego sygnał:

Super narzędzie jest gotowe do testu.

Stwórz plik app.js i dodaj go do html. Na stronie dodaj miejsce testowe gdzie wrzucisz slider, a następnie w pliku app.js odpal naszą klasę:

Sprawdz czy podczas przesuwania wskaźnika coś się pojawia w konsoli. Jak się pojawia, lecimy dalej.

Klasa ColorSlider

Kolejna klasa to główny obszar, na którym użytkownik będzie pobierać kolor. Będzie ona wyglądać bardzo podobnie do powyżej:

Podobnie jak poprzednio wpierw tworzymy odpowiednie elementy:

Tło tym razem będzie składać się z dwóch gradientów nałożonych na pojedynczy kolor:

Kolejna rzecz to przesuwanie wskaźnika po planszy - bardzo podobnie do poprzedniej klasy:

W przeciwieństwie do poprzedniej klasy doszła nam tutaj funkcja updatePickerColor(). Jeżeli użytkownik zmieni barwę w hue slider, musimy na to zareagować i przerysować powyższe płótno, ale dodatkowo ustawić wskaźnik w odpowiednim miejscu (w sumie moglibyśmy go nie ruszać, ale to nas nic nie będzie kosztowało).

I tak jak poprzednio przydadzą nam się też funkcje służące do ustawiania i pobierania aktualnego koloru.

Do pobrania koloru podejdziemy nieco inaczej niż przy poprzedniej klasie. Wystarczy pobrać informacje z planszy w miejscu położenia kursora. Posłużymy się tutaj funkcją this.ctx.getImageData(x, y, width, height) (linia 25), która zwraca nam wycinek canvasu. W tym przypadku chcemy pobrać tylko jeden piksel, stąd width i height ustawiamy na 1:

Klasa gotowa. Stwórzmy więc na jej bazie nowy element w głownej klasie:

I zepnijmy oba slidery ze sobą podpinając się pod odpowiednie sygnały:

Przy okazji sprawdźmy w pliku app.js czy funkcja setColor() pozwoli nam ustawić domyślny kolor:

Input i button

Kolejnymi w kolejce są input i button. Dodamy je w głównej klasie.

A także w pliku app.js podepnijmy się testowo pod powyższe sygnały

Klasa ColorLibrary

Ostatnim elementem będzie biblioteka kolorów, które użytkownik będzie mógł sobie zapisać.

Początek podobny co poprzednio:

Po kliknięciu na przycisk dodawania koloru musimy pobrać z głównej planszy aktualnie wybrany kolor, a następnie dodać do do kolorów. Niestety w tym przypadku komunikacja za pomocą sygnałów się nie sprawdzi, bo nie reagujemy na akcję, a po prostu chcemy odpalić funkcję getColor(). Przekażmy więc referencję do tamtego obiektu jako parametr tej klasy:

Tworzenie kolorów rozbijemy na dwa kroki. Jedna funkcja createColors() będzie robić pętlę po kolorach i wywoływać będzie drugą funkcję createColorElement() służącą do tworzenia pojedynczego elementu z kolorem:

Dodajmy też dwie funkcje służące do dodawania i usuwania koloru z bilioteki:

Żeby cała biblioteka kolorów miała jakikolwiek sens, dodajmy do niej zapisywanie stanu w LocalStorage.

Klasa praktycznie gotowa. Podłączmy ją w głównej klasie:

W 15 linii ustawiliśmy bibliotece id "colors" (drugi parametr) dzięki czemu w LocalStorage zostanie zapisana pod nazwą "colorPicker-colors". Zakładam, że na stronie możemy mieć kilka color pickerów i każdy powinien móc korzystać z dowolnej biblioteki - albo współdzielonej albo indywidualnej. Dlatego te id powinny być podawane przez programistę. Podobnie do innych projektów w tym kursie (np. tenarrow-up-right) dodajmy możliwość przekazywania opcji do naszej klasy:

i przekażmy przykładowe opcje w pliku app.js:

Czas na testy. Tak jak poprzednio dodajmy do html dwa dodatkowe miejsca testowe, a następnie w app.js wrzućmy do nich 2 dodatkowe pickery:

Bonus: wspólne biblioteki

W powyższym przykładzie mamy 3 color pickery, gdzie 2 z nich używają tej samej biblioteki kolorów. Niestety nie są one zsynchronizowane - gdy w jednym z nich dodam lub usunę kolor, w drugim pickerze nie zostanie to odwzorowane. Żeby móc coś takiego zrobić, dodajmy do klasy dodatkowy sygnał oraz funkcję aktualizującą bibliotekę w danym pickerze:

i wykorzystajmy je w głównym pliku:

Jeżeli bardzo ci zależy, możesz też powyższy mechanizm dodać do głównej klasy ColorPicker, dzięki czemu nie będziesz musiał pamiętać o takiej synchronizacji.

Demo

I w zasadzie tyle. Przydało by się jeszcze dodać jakieś dodatkowe opcje, które moglibyśmy podawać (np. czy pokazywać poszczególne elementy - np. bibliotekę koloru, ustawienie tekstu na przycisku itp.), ale to już zostawiam tobie.

W poniższym demie nie bundlowałem wszystkiego jakimiś narzędziami. Zamiast tego wywaliłem wszystkie import/export z kodu i wrzuciłem wszystko do jednego pliku dodatkowo zabezpieczając wszystko przed niepowołanym dostępem:

Last updated