Powiąż elementy ze sobą za pomocą pozycjonowania zakotwiczenia CSS

.

Jak obecnie można połączyć jeden element z innym? Możesz śledzić ich pozycje lub użyć elementu opakowania.

<!-- index.html -->
<div class="container">
  <a href="/link" class="anchor">I’m the anchor</a>
  <div class="anchored">I’m the anchored thing</div>
</div>
/* styles.css */
.container {
  position: relative;
}
.anchored {
  position: absolute;
}

Takie rozwiązania często nie są idealne. Wymagają one JavaScriptu lub wprowadzają dodatkowe znaczniki. Interfejs API do pozycjonowania kotwic CSS ma rozwiązać ten problem, udostępniając interfejs CSS API do łączenia elementów. Umożliwia ona ustawienie położenia i rozmiaru jednego elementu na podstawie położenia i rozmiaru innych elementów.

Obraz pokazujący mockup okna przeglądarki z szczegółami etykietki.

Obsługa przeglądarek

Interfejs API do pozycjonowania kotwic CSS możesz wypróbować w Chrome Canary za pomocą flagi „Experimental Web Platform Features”. Aby włączyć tę flagę, otwórz Chrome Canary i wejdź na stronę chrome://flags. Następnie włącz flagę „Eksperymentalne funkcje platformy internetowej”.

Zespół Oddbird opracowuje też polyfill. Sprawdź repozytorium github.com/oddbird/css-anchor-positioning.

Obsługę kotwiczenia możesz sprawdzić za pomocą:

@supports(anchor-name: --foo) {
  /* Styles... */
}

Pamiętaj, że ten interfejs API jest nadal w fazie eksperymentalnej i może ulec zmianie. W tym artykule omówiono najważniejsze kwestie. Obecna implementacja nie jest też w pełni zgodna ze specyfikacją grupy roboczej CSS.

Problem

Dlaczego warto to zrobić? Typowym zastosowaniem jest tworzenie etykiet lub elementów podobnych do etykiet. W takim przypadku często warto połączyć tekst podręczny z treścią, do której się odnosi. Często trzeba połączyć jeden element z innym. Oczekujesz też, że interakcja z tą stroną nie spowoduje zerwania połączenia, np. jeśli użytkownik przewinie lub zmieni rozmiar interfejsu.

Kolejnym problemem jest to, że chcesz mieć pewność, że element połączony pozostaje w polu widzenia – na przykład, jeśli otworzysz etykietkę narzędzia, a on zostanie przycięty przez granice widoku. Może to być niewygodne dla użytkowników. Chcesz dostosować tekst podpowiedzi.

Obecne rozwiązania

Obecnie możesz rozwiązać ten problem na kilka sposobów.

Najpierw omówię prymitywną metodę „owijania kotwicy”. Oba elementy są umieszczane w kontenerze. Następnie możesz użyć position, aby ustawić pozycję opisu względem kotwicy.

<div class="containing-block">
  <div class="tooltip">Anchor me!</div>
  <a class="anchor">The anchor</a>
</div>
.containing-block {
  position: relative;
}

.tooltip {
  position: absolute;
  bottom: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%);
}

Możesz przesuwać kontener, a wszystko pozostanie na swoim miejscu.

Innym podejściem może być znane położenie kotwicy lub możliwość jej śledzenia. Możesz go przekazać do etykietki za pomocą właściwości niestandardowych.

<div class="tooltip">Anchor me!</div>
<a class="anchor">The anchor</a>
:root {
  --anchor-width: 120px;
  --anchor-top: 40vh;
  --anchor-left: 20vmin;
}

.anchor {
  position: absolute;
  top: var(--anchor-top);
  left: var(--anchor-left);
  width: var(--anchor-width);
}

.tooltip {
  position: absolute;
  top: calc(var(--anchor-top));
  left: calc((var(--anchor-width) * 0.5) + var(--anchor-left));
  transform: translate(-50%, calc(-100% - 10px));
}

Co jednak, jeśli nie znasz pozycji kotwicy? Prawdopodobnie musisz interweniować za pomocą JavaScriptu. Możesz użyć kodu podobnego do tego, ale oznacza to, że style zaczynają przenikać z CSS do JavaScript.

const setAnchorPosition = (anchored, anchor) => {
  const bounds = anchor.getBoundingClientRect().toJSON();
  for (const [key, value] of Object.entries(bounds)) {
    anchored.style.setProperty(`--${key}`, value);
  }
};

const update = () => {
  setAnchorPosition(
    document.querySelector('.tooltip'),
    document.querySelector('.anchor')
  );
};

window.addEventListener('resize', update);
document.addEventListener('DOMContentLoaded', update);

To rodzi pewne pytania:

  • Kiedy obliczać style?
  • Jak obliczyć style?
  • Jak często obliczać style?

Czy to rozwiązało problem? Może to być rozwiązanie dla Twojego przypadku użycia, ale jest jeden problem: nasze rozwiązanie nie dostosowuje się do Twoich potrzeb. Nie odpowiada. Co się stanie, jeśli element zakotwiczony zostanie obcięty przez obszar widoku?

Teraz musisz zdecydować, czy i jak zareagować na tę sytuację. Liczba pytań i podejmowanych decyzji zaczyna rosnąć. Wystarczy, że połączysz jeden element z drugim. W idealnym świecie rozwiązanie samo dostosowuje się do otoczenia i na nie zareaguje.

Aby nieco ułatwić sobie pracę, możesz skorzystać z rozwiązania w JavaScript. W zależności od sposobu ich użycia może to wiązać się z dodatkowymi kosztami i może spowodować problemy z wydajnością. Na przykład niektóre pakiety używają elementu requestAnimationFrame, aby zachować prawidłową pozycję. Oznacza to, że Ty i Twój zespół musicie zapoznać się z pakietem i jego opcjami konfiguracji. W efekcie pytania i decyzje mogą się zmienić, a nie zmniejszyć. To jest część wyjaśnienia, dlaczego warto używać pozycji kotwicy w kodzie CSS. Dzięki temu nie musisz się martwić o problemy z wydajnością podczas obliczania pozycji.

Oto jak może wyglądać kod korzystający z popularnego pakietu „floating-ui” do rozwiązywania tego problemu:

import {computePosition, flip, offset, autoUpdate} from 'https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.2.1/+esm';

const anchor = document.querySelector('.anchor')
const tooltip = document.querySelector('.tooltip')

const updatePosition = () => {  
  computePosition(anchor, tooltip, {
    placement: 'top',
    middleware: [offset(10), flip()]
  })
    .then(({x, y}) => {
      Object.assign(tooltip.style, {
        left: `${x}px`,
        top: `${y}px`
      })
  })
};

const clean = autoUpdate(anchor, tooltip, updatePosition);

Spróbuj ponownie ustawić kotwicę w tym pliku demonstracyjnym, który używa tego kodu.

„Przybornik” może nie działać zgodnie z oczekiwaniami. Reaguje na wyjście poza widoczny obszar wzdłuż osi y, ale nie wzdłuż osi x. Zapoznaj się z dokumentacją, a na pewno znajdziesz rozwiązanie, które będzie dla Ciebie odpowiednie.

Znajdowanie pakietu, który sprawdzi się w Twoim projekcie, może jednak zająć dużo czasu. To dodatkowe decyzje, które mogą być frustrujące, jeśli nie działają tak, jak chcesz.

Używanie pozycjonowania za pomocą kotwicy

Wpisz interfejs API pozycjonowania kotwicy CSS. Chodzi o to, aby zachować style w pliku CSS i zmniejszyć liczbę decyzji, które musisz podjąć. Chcesz uzyskać ten sam wynik, ale Twoim celem jest poprawa komfortu pracy programistów.

  • Nie wymaga JavaScriptu.
  • Pozwól przeglądarce określić najlepszą pozycję na podstawie Twoich wskazówek.
  • Brak zależności od innych firm
  • Brak elementów opakowania.
  • Działa z elementami znajdującymi się na górnej warstwie.

Odtwórzmy i rozwiążmy problem, który próbowaliśmy rozwiązać powyżej. Zamiast tego użyj analogii łodzi z kotwicą. Reprezentują one element z ankre i ankre. Woda reprezentuje blok zawierający.

Najpierw musisz wybrać sposób zdefiniowania kotwicy. Możesz to zrobić w kodzie CSS, ustawiając w elemencie kotwicy właściwość anchor-name. Może mieć wartość z pojedynczymi myślnikami.

.anchor {
  anchor-name: --my-anchor;
}

Możesz też zdefiniować kotwicę w kodzie HTML za pomocą atrybutu anchor. Wartością atrybutu jest identyfikator elementu kotwicy. Spowoduje to utworzenie niejawnego zakotwiczenia.

<a id="my-anchor" class="anchor"></a>
<div anchor="my-anchor" class="boat">I’m a boat!</div>

Po zdefiniowaniu kotwicy możesz użyć funkcji anchor. Funkcja anchor przyjmuje 3 argumenty:

  • Element kotwicy: anchor-name kotwicy, której chcesz użyć, lub możesz pominąć tę wartość i użyć kotwicy implicit. Możesz go zdefiniować za pomocą relacji HTML lub za pomocą właściwości anchor-default z wartością anchor-name.
  • Strefa kotwicy: słowo kluczowe w miejscu, którego chcesz użyć. Może to być top, right, bottom, left, center itp. Możesz też podać odsetek. Na przykład 50% jest równe center.
  • Wartość zastępcza: opcjonalna wartość zastępcza, która może być długością lub procentem.

Funkcję anchor używasz jako wartości właściwości wcięcia (top, right, bottom, left lub ich logicznych odpowiedników) elementu zakotwiczonego. Funkcji anchor możesz też używać w calc:

.boat {
  bottom: anchor(--my-anchor top);
  left: calc(anchor(--my-anchor center) - (var(--boat-size) * 0.5));
}

 /* alternative with anchor-default */
.boat {
  anchor-default: --my-anchor;
  bottom: anchor(top);
  left: calc(anchor(center) - (var(--boat-size) * 0.5));
}

Nie ma właściwości center, więc jeśli znasz rozmiar elementu z ankredą, możesz użyć właściwości calc. Dlaczego nie używać translate? Możesz użyć:

.boat {
  anchor-default: --my-anchor;
  bottom: anchor(top);
  left: anchor(center);
  translate: -50% 0;
}

Przeglądarka nie uwzględnia jednak przekształconych pozycji elementów z ankre. Gdy zaczniesz stosować pozycje zastępcze i automatyczne pozycjonowanie, zrozumiesz, dlaczego jest to ważne.

Być może zauważyłeś/zauważyłaś użycie właściwości niestandardowej --boat-size powyżej. Jeśli jednak chcesz, aby rozmiar elementu zakotwiczonego był zależny od rozmiaru kotwicy, możesz też uzyskać dostęp do tego rozmiaru. Zamiast obliczać to samodzielnie, możesz użyć funkcji anchor-size. Aby na przykład zrobić łódź, która jest w cztery razy szersza od kotwicy:

.boat {
  width: calc(4 * anchor-size(--my-anchor width));
}

Masz też dostęp do wysokości za pomocą usługi anchor-size(--my-anchor height). Możesz go użyć do ustawienia rozmiaru jednej lub obu osi.

Co zrobić, jeśli chcesz zastosować kotwicę do elementu z pozycjonowaniem absolute? Elementy nie mogą być elementami siostrzanymi. W takim przypadku możesz owinąć kotwicę w kontenerze z ustawieniem pozycjonowania relative. Następnie możesz go zablokować.

<div class="anchor-wrapper">
  <a id="my-anchor" class="anchor"></a>
</div>
<div class="boat">I’m a boat!</div>

Obejrzyj tę wersję demonstracyjną, w której możesz przeciągać kotwicę, a łódź będzie podążać za nią.

Śledzenie pozycji przewijania

W niektórych przypadkach element kotwicy może znajdować się w kontenerze, którego zawartość można przewijać. Jednocześnie element zakotwiczony może znajdować się poza tym kontenerem. Przewijanie odbywa się w innym wątku niż układ, dlatego musisz mieć możliwość śledzenia tego procesu. Może to zrobić właściwość anchor-scroll. Ustawiasz go na elemencie z kotwicą i przypisujesz mu wartość kotwicy, którą chcesz śledzić.

.boat { anchor-scroll: --my-anchor; }

Wypróbuj tę prezentację, w której możesz włączać i wyłączać anchor-scroll za pomocą pola wyboru w rogu.

Analogia nie do końca pasuje, ponieważ w idealnym świecie łódź i kotw są w wodzie. Ponadto funkcje takie jak interfejs Popover API umożliwiają utrzymywanie powiązanych elementów w pobliżu. Umieszczenie za pomocą kotwicy będzie jednak działać w przypadku elementów znajdujących się na górnej warstwie. To jedna z głównych zalet interfejsu API: możliwość łączenia elementów w różnych przepływach.

Rozważ ten demonstracyjny kontener z przewijaniem, który zawiera kotwy z hiperlinkami. Elementy tooltipa, które są wyskakującymi etykietami, mogą nie być zlokalizowane razem z ankramami:

Zauważ jednak, że popovery śledzą odpowiednie linki do kotwic. Możesz zmienić rozmiar tego ruchomego kontenera, a pozycje zostaną zaktualizowane.

Położenie reklamy zastępczej i automatyczne pozycjonowanie

W tym miejscu moc pozycjonowania kotwicy wzrasta. position-fallback może ustawić element zakotwiczony na podstawie podanych przez Ciebie wartości zastępczych. Tutaj to Ty określasz styl, a przeglądarka sama dobiera pozycję.

Typowym przypadkiem użycia jest tu tekst, który powinien być wyświetlany nad lub pod kotwicą. To zachowanie zależy od tego, czy tooltip zostanie przycięty przez kontener. Ten kontener to zwykle widoczny obszar.

Jeśli przyjrzysz się kodom ostatniej prezentacji, zauważysz, że użyto w niej właściwości position-fallback. Jeśli przewijałeś kontener, mogłeś zauważyć, że te zakotwiczone wyskakujące okienka przesunęły się. Dzieje się tak, gdy ich odpowiednie kotwy zbliżają się do granicy widoku. W tym momencie wyskakujące okienka próbują się dostosować, aby pozostać w obszarze widoku.

Zanim utworzysz wyraźne position-fallback, pozycjonowanie za pomocą kotwic będzie również oferować automatyczne pozycjonowanie. Możesz to zrobić bezpłatnie, używając wartości auto zarówno w przypadku funkcji kotwicy, jak i właściwości przeciwległego wstawienia. Jeśli na przykład użyjesz wartości anchor dla bottom, ustaw wartość top na auto.

.tooltip {
  position: absolute;
  bottom: anchor(--my-anchor auto);
  top: auto;
}

Alternatywą dla automatycznego pozycjonowania jest użycie jawnego position-fallback. W tym celu musisz zdefiniować zestaw zastępczy pozycji. Przeglądarka będzie sprawdzać te wartości, aż znajdzie taką, której może użyć, a potem zastosuje to ustawienie. Jeśli nie znajdzie odpowiedniego, użyje domyślnie pierwszego zdefiniowanego.

position-fallback, który próbuje wyświetlić etykiety powyżej i poniżej, może wyglądać tak:

@position-fallback --top-to-bottom {
  @try {
    bottom: anchor(top);
    left: anchor(center);
  }

  @try {
    top: anchor(bottom);
    left: anchor(center);
  }
}

Oto jak wyglądają tooltipy po zastosowaniu tych ustawień:

.tooltip {
  anchor-default: --my-anchor;
  position-fallback: --top-to-bottom;
}

Użycie anchor-default oznacza, że możesz ponownie użyć elementu position-fallback w innych elementach. Możesz też użyć właściwości niestandardowej z ograniczonym zakresem, aby ustawić wartość anchor-default.

Rozważ to demo z powrotem na łodzi. Urządzenie ma ustawiony position-fallback. Gdy zmieniasz pozycję kotwicy, łódź dostosowuje się, aby pozostać w kontenerze. Spróbuj też zmienić wartość wypełnienia, która dostosowuje wypełnienie tekstu. Zwróć uwagę, jak przeglądarka koryguje pozycjonowanie. Pozycje zmieniają się, gdy zmieniasz wyrównanie siatki kontenera.

Tym razem position-fallback jest bardziej szczegółowy i próbuje ustawić pozycje zgodnie z kierunkiem ruchu wskazówek zegara.

.boat {
  anchor-default: --my-anchor;
  position-fallback: --compass;
}

@position-fallback --compass {
  @try {
    bottom: anchor(top);
    right: anchor(left);
  }

  @try {
    bottom: anchor(top);
    left: anchor(right);
  }

  @try {
    top: anchor(bottom);
    right: anchor(left);
  }

  @try {
    top: anchor(bottom);
    left: anchor(right);
  }
}


Przykłady

Teraz, gdy znasz już główne funkcje pozycjonowania kotwic, przyjrzyjmy się kilku interesującym przykładom poza etykietami narzędzi. Te przykłady mają na celu rozbudzenie Twojej kreatywności i wskazanie sposobów korzystania z pozycji kotwicy. Najlepszym sposobem na udoskonalenie specyfikacji jest zebranie opinii prawdziwych użytkowników takich jak Ty.

Menu kontekstowe

Zacznijmy od menu kontekstowego korzystającego z interfejsu Popover API. Po kliknięciu przycisku ze strzałką pojawi się menu kontekstowe. To menu będzie miało własne menu, które można rozwinąć.

W tym przypadku znaczniki nie są ważne. Ale masz 3 przyciski, z których każdy korzysta z funkcji popovertarget. Następnie masz 3 elementy, które używają atrybutu popover. Dzięki temu możesz otwierać menu kontekstowe bez JavaScriptu. Może to wyglądać tak:

<button popovertarget="context">
  Toggle Menu
</button>        
<div popover="auto" id="context">
  <ul>
    <li><button>Save to your Liked Songs</button></li>
    <li>
      <button popovertarget="playlist">
        Add to Playlist
      </button>
    </li>
    <li>
      <button popovertarget="share">
        Share
      </button>
    </li>
  </ul>
</div>
<div popover="auto" id="share">...</div>
<div popover="auto" id="playlist">...</div>

Możesz teraz zdefiniować position-fallback i udostępniać go w menu kontekstowych. Pamiętaj, aby w przypadku wyskakujących okienek wyłączyć wszystkie style inset.

[popovertarget="share"] {
  anchor-name: --share;
}

[popovertarget="playlist"] {
  anchor-name: --playlist;
}

[popovertarget="context"] {
  anchor-name: --context;
}

#share {
  anchor-default: --share;
  position-fallback: --aligned;
}

#playlist {
  anchor-default: --playlist;
  position-fallback: --aligned;
}

#context {
  anchor-default: --context;
  position-fallback: --flip;
}

@position-fallback --aligned {
  @try {
    top: anchor(top);
    left: anchor(right);
  }

  @try {
    top: anchor(bottom);
    left: anchor(right);
  }

  @try {
    top: anchor(top);
    right: anchor(left);
  }

  @try {
    bottom: anchor(bottom);
    left: anchor(right);
  }

  @try {
    right: anchor(left);
    bottom: anchor(bottom);
  }
}

@position-fallback --flip {
  @try {
    bottom: anchor(top);
    left: anchor(left);
  }

  @try {
    right: anchor(right);
    bottom: anchor(top);
  }

  @try {
    top: anchor(bottom);
    left: anchor(left);
  }

  @try {
    top: anchor(bottom);
    right: anchor(right);
  }
}

Dzięki temu masz do dyspozycji adaptacyjny interfejs menu kontekstowego z zagnieżeniami. Spróbuj zmienić pozycję treści za pomocą selektora. Wybrana opcja zaktualizuje wyrównanie siatki. To wpływa na sposób, w jaki pozycjonowanie kotwicy określa położenie wyskakujących okienek.

Skupienie i śledzenie

W tym pokazie łączymy proste elementy CSS za pomocą funkcji :has(). Chodzi o to, aby przeprowadzić przejście wizualne dla elementu input, który ma fokus.

Aby to zrobić, ustaw nowy punkt zakotwiczenia w czasie wykonywania. W tym pokazie ograniczona usługa niestandardowa jest aktualizowana po skupieniu się na danych wejściowych.

#email {
    anchor-name: --email;
  }
  #name {
    anchor-name: --name;
  }
  #password {
    anchor-name: --password;
  }
:root:has(#email:focus) {
    --active-anchor: --email;
  }
  :root:has(#name:focus) {
    --active-anchor: --name;
  }
  :root:has(#password:focus) {
    --active-anchor: --password;
  }

:root {
    --active-anchor: --name;
    --active-left: anchor(var(--active-anchor) right);
    --active-top: calc(
      anchor(var(--active-anchor) top) +
        (
          (
              anchor(var(--active-anchor) bottom) -
                anchor(var(--active-anchor) top)
            ) * 0.5
        )
    );
  }
.form-indicator {
    left: var(--active-left);
    top: var(--active-top);
    transition: all 0.2s;
}

Jak możesz to wykorzystać? Możesz go użyć jako nakładki instruktażowo-edukacyjnej. Etykieta może się przemieszczać między punktami zainteresowania i zmieniać swoją treść. Możesz użyć przejścia krzyżowego. W tym przypadku mogą się przydać animacje umożliwiające animowanie display lub przejść widoku.

Obliczanie wykresu słupkowego

Inną ciekawą rzeczą, którą możesz zrobić z pozycjonowaniem kotwicy, jest połączenie go z calc. Wyobraź sobie wykres z wyskakującymi okienkami zawierającymi adnotacje.

Najwyższe i najniższe wartości możesz śledzić za pomocą selektora CSS minmax. Kod CSS może wyglądać tak:

.chart__tooltip--max {
    left: anchor(--chart right);
    bottom: max(
      anchor(--anchor-1 top),
      anchor(--anchor-2 top),
      anchor(--anchor-3 top)
    );
    translate: 0 50%;
  }

Do aktualizowania wartości wykresu służy kod JavaScript, a do nadawania mu stylu – kod CSS. Jednak pozycjonowanie za pomocą kotwicy pozwala nam na aktualizację układu.

Uchwyty zmiany rozmiaru

Nie musisz stosować kotwicy tylko do jednego elementu. Do elementu możesz użyć wielu kotwic. Możesz to zauważyć na przykładzie wykresu słupkowego. Wskazówki były przymocowane do wykresu, a potem do odpowiedniego słupka. Jeśli rozwiniesz tę koncepcję, możesz użyć jej do zmiany rozmiaru elementów.

Punkty kotwiczenia możesz traktować jak uchwyty do niestandardowego zmiany rozmiaru i użyć wartości inset.

.container {
   position: absolute;
   inset:
     anchor(--handle-1 top)
     anchor(--handle-2 right)
     anchor(--handle-2 bottom)
     anchor(--handle-1 left);
 }

W tym pokazie komponent GreenSock Draggable sprawia, że uchwyty można przeciągać. Element <img> zmienia jednak rozmiar, aby wypełnić kontener, który dostosowuje się do luki między uchwytami.

SelectMenu?

Ten ostatni jest trochę zachętą do tego, co będzie dalej. Możesz jednak utworzyć wyskakujące okienko, które można ustawić w centrum uwagi. Możesz utworzyć podstawy elementu <select>, który można stylizować.

<div class="select-menu">
<button popovertarget="listbox">
 Select option
 <svg>...</svg>
</button>
<div popover="auto" id="listbox">
   <option>A</option>
   <option>Styled</option>
   <option>Select</option>
</div>
</div>

Użycie domyślnego anchor ułatwi to zadanie. Jednak kod CSS dla prostego punktu wyjścia może wyglądać tak:

[popovertarget] {
 anchor-name: --select-button;
}
[popover] {
  anchor-default: --select-button;
  top: anchor(bottom);
  width: anchor-size(width);
  left: anchor(left);
}

Połącz funkcje interfejsu Popover API z pozycjonowaniem za pomocą kotwicy CSS, a będziesz już blisko.

To świetne, gdy zaczynasz wprowadzać takie rzeczy jak :has(). Możesz obrócić znacznik po otwarciu:

.select-menu:has(:open) svg {
  rotate: 180deg;
}

Co możesz zrobić dalej? Co jeszcze jest potrzebne, aby select działało prawidłowo? Zostawimy to na kolejny artykuł. Nie martw się, już wkrótce pojawią się elementy do wybrania, które można stylizować. Więcej informacji już wkrótce.


To wszystko.

Platforma internetowa się rozwija. Umiejscowienie kotwicy w CSS jest kluczowe dla usprawnienia procesu tworzenia elementów sterujących interfejsu. Dzięki temu nie będziesz musiał podejmować trudnych decyzji. Pozwoli Ci też wykonywać czynności, które wcześniej nie były możliwe. Na przykład stylizacja elementu <select>. Powiedz nam, co o nim sądzisz.

Zdjęcie autorstwa CHUTTERSNAPUnsplash