Kawałek Kodu

Kilka miesięcy wstecz trafiłem na... efekt, plugin, kontrolkę, w sumie trudno nazwać czym jest comparison slider. Jego działanie objawia się tym, że w jego oknie widzimy dwa obrazy, które rozdzielone są suwakiem. Po lewej stronie suwaka widzimy lewy obraz, a po prawej... W zależności od przesunięcia suwaka wyłania się większa część danej połówki. Tak więc trafiłem i zapomniałem. Parę dni temu przypomniałem sobie o tym efekcie i postanowiłem go odtworzyć w CSS, wpierw wyszukując czy ktoś już tego nie dokonał.

Okazuje się, że dokonał. Jest kilka rozwiązań, ale z tego co zauważyłem wszystkie wykorzystują właściwość resize elementu. Albo na zwykłym DIV, albo na TEXTAREA. Jak to mówią, nie ma co wynajdywać comparison slidera na nowo, więc pomyślałem o innych sposobach. Takich, które wykorzystują inne natywne właściwości elementów HTML, czyli supermoce związane z przesuwaniem.

Aha. Jeśl nie wiesz czym jest comparison slider to zerknij na ten link, to jeden z przykładowych sliderów. W przydatnych linkach znadziejsz odnośniki do wspomnianych sliderów purecss.

Go, go, Input Range(rs)!

Pierwszy pomysł jaki mi się objawił, to wykorzystanie INPUT o typie range. Czyli suwaka samego w sobie, ale jak można zauważyć ma on element, za który można złapać i ciągnąć, jak również element ten dzieli suwak na dwie części. Niestety te dwie części są dwiema tylko wizualnie, bo jeśli chodzi o możliwości stylowania, to ścieżka suwaka nadal jest jednym elementem. Wyjątkiem jest przeglądarka Edge, gdzie mamy dostępo do stylowania zarówno obszaru przed, jak i za suwakiem. Z wiadomych względów nie będziemy się jednak rozwodzić nad tym rozwiązaniem. Czy ta wada dyskwalifikuje tą opcję? Oczywiście, że nie, przecież nie ma rzeczy niemożliwych. No, prawie. Jest kilka rzeczy niemożliwych, a oprócz nich niestety ten wariant nie będzie działał poprawnie na przeglądarkach mobilnych, bo nie jest na nich wspierana wartość fixed dla background-attachment. Ale będzie i drugi!

Zaczniemy od kodu:

<input type="range" min="0" max="99" value="99"/>

+

/* cały nasz input/slider
   będzie mieć wymiary obrazka,
   czyli 300x300
*/
input[type="range"] {
  height: 300px;
  width: 300px;
  margin: 0;
  background: url(photo-editing-before.jpg);
}

input[type="range"]:focus {
  outline: 0;
}

/* wyłączamy natywne stylowanie
*/
input[type="range"],
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
}

/* ścieżka na pełną wysokość
*/
input[type="range"]::-webkit-slider-runnable-track {
  height: 100%;
}

/* gałka na 50% szerokości
   suwaka i to ona będzie
   stanowić drugi obraz
*/
input[type="range"]::-webkit-slider-thumb {
  height: 100%;
  width: 50%;
  background: url(photo-editing-after.jpg) fixed;
  border-left: 2px dotted #000;
  box-sizing: content-box;
  cursor: ew-resize;
}

/* a tu powtarzamy style dla Gecko
*/
input[type=range]::-moz-range-thumb {
  height: 100%;
  width: 50%;
  background: url(photo-editing-after.jpg) fixed;
  border-left: 2px dotted #000;
  box-sizing: content-box;
  cursor: ew-resize;
}

Tak jak pisałem wyżej nie ma możlwości ostylowania odrębnie ścieżka za gałką suwaka, stąd sama gałka przejęła rolę tej drugiej połówki. Ustawiając jej tło na fixed, aby przy poruszaniu zostawało w miejscu, powinniśmy otrzymać to co chcemy.

Hmm... chyba jednak nie. Gałka ma 50% szerokości, ale nie możemy jej przesunąć za prawą krawędź, a kiedy dosuwamy do lewej, to po prawej widzimy tło, którego używa lewa połówka. Być może teraz część z Was wpadła na pomysł, aby w takim razie nadać gałce 100% szerokości. Niestety w takiej sytuacji nie będzie w ogóle możliwe przesuwanie gałką, bo rozpanoszy się na całej szerokości ścieżki niczym zawodnik sumo.

Ale jest w tym pomyśle jakaś logika. Bo na 100% potrzebujemy gałki o 100% szerokości tła, tak aby dosuwając ją do lewej zakryła całą ścieżkę i pokazała właśnie to tło. Skoro nie możemy ustawić szerszej gałki w tym momencie, to trzeba powiększyć dwukrotnie ścieżkę. Kiedy powiększymy ścieżkę, to i gałka jako 50% szerokości ścieżki zyska 100% tła. Tyle, że kiedy powiększymy ścieżkę, to tło dla niej nie wypełni jej 100% szerokości, ale zaledwie połowę. Zmieniając tylko te parametry w sumie nie zyskamy na niczym, a raczej stracimy. Trzeba zakombinować inaczej. Skoro ścieżka ma mieć teraz 600px, ale ma być jej widać tylko 300px, to musimy nasz INPUT schować w dodatkowym elemencie o szerkości 300px. INPUT ustawimy tak, aby było widać tylko jego 50%, czyli 300px. Gałka będzie mieć 50% z 600px (ze ścieżki), więc też będzie widać jej 300px. Obydwa elementy wizualnie będą mieć tą samą szerokość.

<div>
  <input type="range" min="0" max="99" value="99"/>
</div>

+ (style CSS dla Gecko należy skopiować z ostatniej reguły)

div {
  width: 300px;
  height: 300px;
  position: relative;
}

input[type="range"] {
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  width: 200%;
  margin: 0;
  background: url(photo-editing-before.jpg);
}

input[type="range"]:focus {
  outline: 0;
}

input[type="range"],
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
}

input[type="range"]::-webkit-slider-runnable-track {
  height: 100%;
}

input[type="range"]::-webkit-slider-thumb {
  height: 100%;
  width: 50%;
  background: url(photo-editing-after.jpg) fixed;
  border-left: 2px dotted #000;
  box-sizing: content-box;
  cursor: ew-resize;
}

W tym przykładzie jesteśmy już bliżej. Kontener ma 300px, INPUT ma 600px. Nie widać jednak jego 300px, bo kontener nie ma ustawionego overflow na wartość hidden.

Ooo! Teraz lepiej. Można jednak zauważyć, że gałka ma punkt uchwytu w swojej połowie, więc wcale nie przesuwam za kropkowaną linię. Tu cudem pomaga nam translacja, która nie tylko przesuwa gałkę, ale i punkt uchwytu:

input[type="range"]::-webkit-slider-thumb {
/* wcześniejsze właściwości
   +
*/
  transform: translateX(50%);
}

Jest lepiej, ale i gorzej. Co prawda punkt uchwytu zmienił się, ale teraz gałka zaczyna swoją podróż w połowie kontenera, a kończy o połowę za daleko. Ale skoro przesunęliśmy ją o 50% w prawo, to nie ma zdziwka. Na to jednak też jest rozwiązanie. Wystarczy pociągnąć teraz w lewo INPUT, a gałka wróci na swoje miejsce. Przesuwamy go o 25% w lewo. Dlaczego o 25%? Bo ma on 600px, a nam zależy aby przesunąć o 150px.

input[type="range"] {
/* wcześniejsze właściwości
   +
*/
  transform: translate(-25%);
}

Można oszaleć. Czego się człowiek nie dotknie, to co innego się psuje. Ale spokojnie, i temu da się zaradzić. Teraz należy przesunąć tło o 150px w prawo (czyli o 50% swojej szerokości) i wszystko wraca do normy!

input[type="range"] {
/* wcześniejsze właściwości
   +
*/
  background-position: 50% 0;
}

Jeden zwój mózgowy.

Drugie rozwiązanie oparte jest o scrollbar. Tak, tak, będzie to ostylowany suwak. Tu też nie będzie tak słodko, bo ostylowany jest możliwościami Chromium, czyli na Firefox nie będzie działać (ale działa na mobilnym Chrome). Niestety nie ma możliwości ostylowania na Firefox, bo ta przeglądarka nie obsługuje podobnych do Chrome właściwości. Te magiczne właściwości to:

  • ::-webkit-scrollbar - kontener scrollbara,
  • ::-webkit-scrollbar-thumb - gałka do przesuwania,
  • ::-webkit-scrollbar-track-piece:end - część ścieżki na prawo od gałki

Tym razem potrzebujemy jeden element:

<div></div>

+

div {
  height: 300px;
  width: 300px;
/* wymuszamy tylko
   poziomy scrollbar
*/
  overflow-x: scroll;
  overflow-y: hidden;
/* nie możemy ustawić
   stylu kursora dla
   scrollbara, więc
   ustawiamy dla
   kontenera
*/
  cursor: ew-resize;
}

/* rozpychamy kontener,
   aby suwak był
   "przesuwny"
*/
div:after {
  display: block;
  content: "";
  width: 5000px;
  height: 1px;
}

/* scrollbar z lewym
   tłem
*/
::-webkit-scrollbar {
  height: 300px;
  background: url(photo-editing-before.jpg);

}

/* gałka będzie pełnić
   rolę rozdzielacza
   połówek
*/
::-webkit-scrollbar-thumb {
  background: #888;
}

/* ścieżka na prawo
   od gałki
*/
::-webkit-scrollbar-track-piece:end {
  background: url(photo-editing-after.jpg) right center;
}

Teraz kilka odpowiedzi na dziwności z CSS:

  1. Pseudoelement ma 5000px szerokości, aby dosyć dobrze rozpechnąć kontener. To jedyna opcja aby zmniejszyć szerokość gałki, czyli naszego rozdzielacza. Niestety mniejszego już nie da się zrobić. Tzn. istnieje jedno rozwiązanie, tj. ustalenie przezroczystego tła i wstawienie wyśrodkowanego, rozciągniętego w dół jednopikselowego tła, ale to niestety nie działa na mobilnym Chrome. Pozostawiłem więc tak.
  2. Dla scrollbara nie jest ustawiona wysokość 100%, bo tak nie działa, trzeba ustawić explicit.
  3. Tło dla prawej części ścieżki jest wyrównane do prawej, dzięki czemu przy wydłużaniu tej części pozostaje nadal na swoim miejscu - odkrywa się tylko lewa część tła. Emuluje to nam background-attachment: fixed.
  4. Jest jeszcze jedna dziwność niewidoczna w CSS. Mianowicie rozdzielacz jest aktywny w swojej górnej połowie. Dlaczego tak się dzieje, nie mam pojęcia. Nic go nie zasłania, kombinacje z pointer-events nie przyniosły skutku. Może, Ty, Czytelniku, wiesz dlaczego tak się dzieje?

Czytelniku, jak śpiewała Sinéad O'Connor "nothing compares to you", więc z niecierpliwością czekam, aż Ty, a nie nikt inny, znów się tu pojawisz. Do przeczytania!

 

Przydatne linki:
Reguły styli dla scrollbara na Chrome.
Comparison slider autorstwa Lei Verou z użyciem DIV.
Comparison slider autorstwa Beben Koben z użyciem DIV.
Comparison slider autorstwa Kseso z użyciem TEXTAREA.
Zdjęcie wykorzystane w sliderze.