Strona główna / Artykuły / Gatsby - Stylowanie i obrazki

Gatsby - Stylowanie i obrazki

W tej części zaczynamy pracę z realnym projektem, który na koniec tego kursu będzie mógł znaleźć się w twoim portfolio. Poznasz dziś pluginy, metody stylowania oraz magiczne obrazki w Gatsbym.

Olaf Sulich

Zobacz wszystkie części kursu Gatsby

Zanim przejdziemy do praktyki, zastanówmy się jaki mamy wybór w kwestii stylowania naszej aplikacji. Gatsby nie ogranicza nas w tej kwestii, możemy pisać w czystym CSS, korzystać z dobrodziejstw Sassa lub tworzyć komponenty w Styled Components.

Postanowiłem jednak, że przedstawię Ci coś, co ostatnio podbija serca front-end deweloperów - Tailwind CSS. Tailwinda możemy połączyć z innym rozwiązaniem np. CSS modules, tak też zrobimy w naszym projekcie, zaczynajmy!

Czym jest Tailwind?

Jeśli jeszcze nie widziałaś/eś filmu Przemka o Tailwindzie, gorąco zachęcam, będzie to idealne wprowadzenie przed tą lekcją.

Tailwind to CSSowy framework, który oparty jest o tzw. atomowe klasy. Każda klasa określa jeden konkretny styl. Na przykład, klasa m-10 ustawi margin elementu na 2.5rem.

Przykładowy komponent karty w Tailwind

Dzięki temu frameworkowi możemy prototypować i tworzyć pełnoprawne layouty, szybciej i mniejszym kosztem. Klasy są tak nazwane, że na pewno szybko się w nich połapiesz!

To, co odstrasza ludzi w Tailwindzie to fakt, że nasz html, wraz z rozwojemy projektu, jest zasypany dziesiątkami klas, jest na to rozwiązanie. Nic nie stoi nam na przeszkodzie, żebyśmy stworzyli osobny plik .css lub .scss i rozszerzyli naszą klasę o klasy z Tailwinda, spójrz jakie to proste:

.btn {
  @apply font-bold py-2 px-4 rounded;
}
.btn-blue {
  @apply bg-blue-500 text-white;
}
.btn-blue:hover {
  @apply bg-blue-700;
} 

Narzędzia i materiały

Zanim zaczniemy pisać kod w Tailwindzie, chciałbym abyś zainstalował/a świetne rozszerzenie do Visual Studio Code - Tailwind CSS IntelliSense. Dzięki temu dostaniemy podpowiedzi, podświetlenie składni i wyłapywanie potencjalnych błędów.

A na sam koniec mały bonus - lista przydatnych narzędzi z Tailwindowego community:

  • Tailblocks - gotowe, responsywne komponenty "bloki" do wykorzystania od ręki
  • Tailwind components - wielka baza mini projektów i komponentów wykonanych przez społeczność
  • Tailwind.run - playground, w którym możesz przetestować swoje style na różnych rozdzielczościach
  • TailwindInk - narzędzie do generowania palet kolorów
  • Tailwind Gradient Designer - generator gradientów wykorzystujący kolory z Tailwinda

Pluginy

W poprzednich wpisach wspominałem Ci o pluginach czyli jednej z największych zalet Gatsbiego! Potrzebujesz wsparcia dla SEO w projekcie? Nie ma sprawy, jest na to plugin. W momencie pisania tego artykułu jest ich aż 2409, a ta liczba wciąż rośnie!

Pierwszy plugin

Taka sama sytuacja tyczy się wsparcia dla Sassa, z którego będziemy korzystać. Zainstalujmy potrzebne paczki:

npm install node-sass gatsby-plugin-sass

Żeby plugin zaczął działać potrzebujemy go dodać do pliku konfiguracyjnego gatsby.config.js:

module.exports = {
  plugins: ['gatsby-plugin-sass'],
};

Konfiguracja Tailwinda

Zainstalujmy teraz Tailwinda i utwórzmy jego plik konfiguracyjny:

npm install tailwindcss --save-dev & npx tailwindcss init

Po wykonanej operacji powinieneś zobaczyć plik tailwind.config.js w katalogu głównym. Możemy w nim dodać customowe fonty, pluginy i dodać kolory. Na obecną chwilę nie będziemy nic tutaj zmieniać.

Żeby Tailwind zadziałał musimy dodać go do naszej konfiguracji Sassa:

module.exports = {
  plugins: [
    {
      resolve: 'gatsby-plugin-sass',
      options: {
        postCssPlugins: [
          require("tailwindcss"),
          require("./tailwind.config.js"),
        ],
      },
    },
  ],
};

Jak widzisz, gatsby-plugin-sass posiada dodatkowe opcje, w tym wypadku jest to opcja dodania pluginów do postCssPlugins. Jeśli chcemy zadeklarować jakieś specjalne opcje dla konkretnego pluginu, zamiast podawać jego nazwę, podajemy cały obiekt, który posiada klucz resolve, to tutaj powinna się znaleźć nazwa pluginu.

W tym przypadku importujemy Tailwinda i jego plik konfiguracyjny za pomocą require(), ale możesz wykorzystać w tym celu również import.

Globalne style

Do stylowania aplikacji będziemy używać Tailwinda i CSS modules, czyli styli, które będą należały tylko do danego komponentu. Chcemy mieć jednak jakiś plik z głównymi stylami, które będą obowiązywały w obrębie całej aplikacji.

Stwórzmy więc katalog styles a w nim plik global.scss:

@tailwind base;
@tailwind components;
@tailwind utilities;

* {
  font-family: "Open Sans", sans-serif;
  box-sizing: border-box;
}

html,
a,
p,
li {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

html {
  box-sizing: border-box;
}

*,
*::before,
*::after {
  box-sizing: inherit;
}

body {
  padding: 0;
  margin: 0;
}

Na samej górze wykorzystujemy tzw. dyrektywy, które najzwyczajniej w świecie dają nam podstawowe style, komponenty z Tailwinda. Dalsza część pliku to już to, co na pewno znasz, czyli style globalne.

Ostatnie co musimy zrobić to dodać nasze style do pliku gatsby-browser.js:

import "./src/styles/global.scss";

Ale hola hola, czym jest ten cały gatsby-browser.js? Jest to jeden z czterech specjalnych plików, który udostępnia nam Gatsby. Pierwszy już poznałaś/eś - był to nasz plik konfiguracyjny gatsby-config.js. gatsby-browser daje nam możliwość interakcji z przeglądarką, to tyle ile powinieneś wiedzieć o nim na ten moment, wrócimy jeszcze do niego w kolejnych materiałach.

Stylowanie headera

Spójrzmy na efekt końcowy, widzimy tutaj nagłówek wraz z opisem, jakiś newsletter, zdjęcie i profil autora. Bierzemy się do pracy!

Strona główna bloga fotografa

Nie chciałbym marnować Twojego czasu, dlatego utworzyłem strukturę headera wraz z klasami w naszym pliku index.js w katalogu pages/. Możesz zobaczyć jak to wygląda na prawdziwym przykładzie. Mamy tutaj wiele małych klas, które odpowiadają za pojedyncze rzeczy, np. w-full odpowiada 100% szerokości, ml-20 to określenie lewego marginesu, a rounded-lg wskazujemy na border radius. Jeśli nie będziesz wiedział/a jaka klasa co robi, wystarczy najechać na nią myszką lub spojrzeć do na prawdę fajnej dokumentacji.

import React from "react";

const Home = () => {
  return (
   <header
      className="w-full bg-gray-900 h-screen flex justify-between"
    >
      <div className="w-1/2 flex justify-between flex-col">
        <div className="ml-20 mt-48">
          <h1 className="text-6xl font-semibold text-white">
            Create amazing <span className="text-blue-500">experience</span>
          </h1>
          <p className="font-medium text-gray-600 text-2xl">
            Hi! I’m Louis and I am professional photograper since 2001. I’m
            writing about cameras, design and photographer stuff
          </p>
          <form className="w-4/6 h-16 bg-white flex justify-between rounded-lg mt-16">
            <label htmlFor="email" className="sr-only">
              Email:
            </label>
            <input
              id="email"
              type="email"
              placeholder="myemail@example.com"
              className="w-full rounded-lg pl-4 py-2"
            />
            <button className="my-3 mr-6 bg-black text-white rounded-lg px-5 py-2">
              Subscribe
            </button>
          </form>
        </div>
        <div className="flex ml-20 mb-6 items-center">
          <img
            src=""
            className="h-16 w-16 rounded-full mr-2"
            alt=""
          />
          <div>
            <p className="text-white font-medium">Louis Edwards</p>
            <p className="text-gray-500">Photographer, design lover</p>
          </div>
        </div>
      </div>
      <div className="w-1/2 justify-end flex">
        <img
          src=""
          className="h-full w-4/5"
          alt="Man taking photo on stairs"
        />
      </div>
    </header>
  );
};

export default Home;

Spokojnie, to jeszcze nie koniec stylowania, musimy w jakiś sposób przyciąć obrazek i załadować pattern dla tła. Takich rzeczy w Tailwindzie nie znajdziemy, więc musimy radzić sobie sami. Ale zanim to zrobimy uporządkujmy trochę nasz kod.

Stwórzmy folder components/, a w nim folder Hero. To w nim będziemy trzymać nasz komponent, wystarczy go tylko stworzyć. Dodajemy do naszego katalogu pliki Hero.js i hero.module.scss. Do tego pierwszego kopiujemy stworzony wcześniej komponent i eksportujemy do strony index.js:

import React from "react";
import Hero from "../components/Hero/Hero";

const Home = () => <Hero />;

export default Home;

Teraz wystarczy nam stworzyć style dla obrazka i tła w pliku hero.module.scss:

.heroImage {
  clip-path: polygon(23% 0, 100% 0%, 100% 100%, 0 100%);
}

.container {
	background-color: #1a202c;
	background-image: url("data:image/svg+xml,%3Csvg...);
}

Cały background-image możesz znaleźć w repozytorium na GitHubie lub pobrać ze strony https://www.heropatterns.com/.

Ostatnim krokiem będzie zaimportowanie styli i wykorzystanie ich w komponencie:

import React from "react";
import styles from "./hero.module.scss";

const Home = () => {
  return (
   <header
      className={`w-full bg-gray-900 h-screen flex justify-between ${styles.container}`}
    >
      <div className="w-1/2 flex justify-between flex-col">
        <div className="ml-20 mt-48">
          <h1 className="text-6xl font-semibold text-white">
            Create amazing <span className="text-blue-500">experience</span>
          </h1>
          <p className="font-medium text-gray-600 text-2xl">
            Hi! I’m Louis and I am professional photographer since 2001. I’m
            writing about cameras, design and photographer stuff
          </p>
          <form className="w-4/6 h-16 bg-white flex justify-between rounded-lg mt-16">
            <label htmlFor="email" className="sr-only">
              Email:
            </label>
            <input
              id="email"
              type="email"
              placeholder="myemail@example.com"
              className="w-full rounded-lg pl-4 py-2"
            />
            <button className="my-3 mr-6 bg-black text-white rounded-lg px-5 py-2">
              Subscribe
            </button>
          </form>
        </div>
        <div className="flex ml-20 mb-6 items-center">
          <img
            src=""
            className="h-16 w-16 rounded-full mr-2"
            alt=""
          />
          <div>
            <p className="text-white font-medium">Louis Edwards</p>
            <p className="text-gray-500">Photographer, design lover</p>
          </div>
        </div>
      </div>
      <div className="w-1/2 justify-end flex">
        <img
					src=""
          className={`h-full w-4/5 ${styles.heroImage}`}
          alt="Man taking photo on stairs"
        />
      </div>
    </header>
  );
};

export default Home;

Obrazki w Gatsbym

Ostylowaliśmy już zdjęcia to teraz przyszedł czas, aby je w końcu załadować. Zanim jednak to zrobimy, musimy się na chwilę zatrzymać przy GraphQL. To przez niego będziemy zaciągali nasze obrazki.

Jeśli wydaję Ci się to głupie, to spokojnie, miałem tak samo gdy zaczynałem uczyć się Gatsbiego. Taki sposób ładowania obrazków daje nam jednak ogromne korzyści. Gatsby może sam zoptymalizować zdjęcia, przygotować je w różnych formatach i na różne urządzenia, jeśli chcemy możemy odpowiednio przyciąć obrazek, żeby wyświetlał się w odpowiednich rozmiarach.

Wygenerowany, przez Gatsbiego, kod obrazka, który zawiera zoptymalizowane wersję obrazków dla różnych rozdzielczości

Pierwsze kroki z GraphQL

Zanim zaczniemy zaciągać obrazki, chciałbym, żebyś dodał/a do swojego gatsby.config.js taki obiekt:

siteMetadata: {
    title: "Photo blog",
  },

Teraz spróbujemy odczytać tytuł naszej strony za pomocą GraphQL. Gatsby udostępnia nam taki GraphQLowy plac zabaw - GraphiQL, to tutaj możemy przetestować nasze zapytania. Odpalmy serwer deweloperski i przejdźmy na http://localhost:8000/___graphql.

Pusty panel GrapiQL

Po lewej stronie możemy wybierać o co chcemy zapytać, jeśli jesteś na bieżąco z tym projektem, ten panel może u Ciebie wyglądać nieco skromniej. Wybierz teraz site, a następnie siteMetadata i title. Klikamy play i naszym oczom powinien ukazać się gotowy wynik zapytania:

Panel GraphiQL, który zawiera zapytanie(wraz z odpowiedzią) o obrazki

Gratulację, wykonałaś/eś właśnie swoje pierwsze zapytanie GraphQL!

Pluginy

Żeby zoptymalizować i wyświetlić obrazki potrzebujemy użyć kilku pluginów:

npm install gatsby-source-filesystem gatsby-transformer-sharp gatsby-plugin-sharp gatsby-image

Instalujemy je i dodajemy do gatsby-confing.js:

const path = require(`path`);

module.exports = {
  siteMetadata: {
    title: "Photo blog",
  },
  plugins: [
    "gatsby-plugin-sharp",
    "gatsby-transformer-sharp",
    {
      resolve: "gatsby-source-filesystem",
      options: {
        name: "images",
        path: path.join(__dirname, "src/assets/images"),
      },
    },
    {
      resolve: "gatsby-plugin-sass",
      options: {
        postCssPlugins: [
          require("tailwindcss"),
          require("./tailwind.config.js"),
        ],
      },
    },
  ],
};

Wykorzystujemy gatsby-source-filesystem do zlokalizowania obrazków, które będą się znajdowały w katalogu images. Dzięki temu pluginowi możemy je odczytać z poziomu GraphQL.

Dwa pierwsze pluginy służą do optymalizacji obrazków, a gatsby-image to komponent, do którego będziemy podawać zaciągniete przez GraphQL obrazki!

Obrazki do pobrania znajdziesz w repozytorium na GitHubie.

Zapytania w GraphQL

Każde zapytanie zaczynamy od specjalnego słowa query, po nim możemy określić nazwę zapytania, ale nie musimy. Jeśli nie czujesz się pewnie w GraphQL możesz sprawdzić mój wpis na ten temat. W zapytaniu wybieramy wszystkie obrazki z opcją fluid i tzw. fragmentem, da nam to bardzo fajny efekt, ale o tym za chwilę.

query {
  allImageSharp {
    edges {
      node {
        fluid {
          ...GatsbyImageSharpFluid_tracedSVG
        }
      }
    }
  }
}

Przejdźmy teraz do strony index.js i wykonajmy zapytanie:

import React from "react";
import { graphql } from 'gatsby'
import Hero from "../components/Hero/Hero";

const Home = ({ data }) => <Hero data={data} />;

export const query = graphql`
	query {
	  allImageSharp {
	    edges {
	      node {
	        fluid {
	          src
	        }
	      }
	    }
	  }
	}
`

export default Home;

Jeśli wykonujemy zapytania na stronach, czyli plikach w katalogu pages/ jedyne co musimy zrobić to zaimportować graphql z Gatsbiego i wyeksportować zapytanie. Dzięki temu dostaniemy gotowe dane w postaci propsa data, które możemy przekazać do komponentu Home.

import React from "react";
import styles from "./hero.module.scss";
import Img from 'gatsby-image';

const Home = ({ data }) => {
  return (
   <header
      className={`w-full bg-gray-900 h-screen flex justify-between ${styles.container}`}
    >
      <div className="w-1/2 flex justify-between flex-col">
			  {...}
        <div className="flex ml-20 mb-6 items-center">
          <Img
            fluid={data.allImageSharp[0].node.fluid}
           className="h-16 w-16 rounded-full mr-2"
            alt=""
          />
          <div>
            <p className="text-white font-medium">Louis Edwards</p>
            <p className="text-gray-500">Photographer, design lover</p>
          </div>
        </div>
      </div>
      <div className="w-1/2 justify-end flex">
        <Img
					fluid={data.allImageSharp[1].node.fluid}
          className={`h-full w-4/5 ${styles.heroImage}`}
          alt="Man taking photo on stairs"
        />
      </div>
    </header>
  );
};

export default Home;

Importujemy komponent Img z paczki gatsby-image. Zwróć uwagę, że nie podajemy mu src, a fluid. Jeśli teraz przeładujesz stronę powinieneś zobaczyć efekt, o którym Ci wspominałem:

Efekt mrugnięcia podczas ładowania obrazka

Obrazki się ładują i w dodatku uzyskujemy, podczas ładowania, efekt obrysu zdjęcia, dzięki temu nie będzie sytuacji, w której użytkownik z wolniejszym połączeniem zobaczy pustą przestrzeń.

Najczęściej do załadowanie zdjęć będziesz wykorzystywał/a fluid, jeśli będziesz chciał/a np. podać konkretne wymiary dla obrazka istnieje bardzo podobna wersja - fixed, w której możemy je podać jako argument.

Komponenty i GraphQL

Nasze pierwsze zapytanie wykonaliśmy na stronie index.js , a co w wypadku, gdy byśmy chcieli je zrobić w komponencie? Do tego mamy specjalnego hooka useStaticQuery , spójrz jak to działa:

import React from "react";
import { useStaticQuery, graphql } from "gatsby";
import Img from "gatsby-image";

const query = graphql`
  query {
    imageSharp(fluid: { src: { regex: "/hero/" } }) {
      fluid {
        ...GatsbyImageSharpFluid_tracedSVG
      }
    }
  }
`;

const Home = () => {
  const data = useStaticQuery(query);
  return (
    <header
      className={`w-full bg-gray-900 h-screen flex justify-between ${styles.container}`}
    >
      {...}
      <div className="w-1/2 justify-end flex">
        <Img
          fluid={data.imageSharp.fluid}
          className={`h-full w-4/5 ${styles.heroImage}`}
          alt="Man taking photo on stairs"
        />
      </div>
    </header>
  );
};

Zapytanie wykonujemy normalnie, tak jak w przypadku stron, ale tym razem nie eksportujemy go, a podajemy do useStaticQuery . Tym razem zamiast wszystkich zdjęć próbuje zaciągnąć jedno konkretne. imageSharp pozwala nam w locie pytać o zdjęcia i tak jak tutaj wybieramy tylko te, które mają w nazwie pliku hero .

Podsumowanie i praktyka

To było dużo wiedzy na jeden raz, żeby ją przyswoić potrzebujesz użyć jej w praktyce, dlatego przygotowałem dla Ciebie mini zdanie, oczywiście tylko dla chętnych.

Twoim zadaniem będzie mały refaktor tego kodu, porozdzielanie komponentu Hero na mniejsze części i złączenie ich w całość. Proponuje, żebyś zapytania wykonał/a w komponentach, zamiast przekazywać propsy.

Jeśli nie chcesz robić tego zadania lub w pewnym etapie się pogubiłeś, końcowy kod dostępny jest w repo na GitHubie.

Zobacz wszystkie części kursu Gatsby

Chcesz być na bieżąco?

Zapisz się na newsletter. Gwarantujemy zero spamu.

Kurs JavaScript?
Mamy coś dla ciebie!

Opanuj JavaScript

Najbardziej praktyczny polski kurs języka JavaScript. 12h lekcji video, 63 ćwiczeń, 9 projektów oraz 800+ quizów - to wszystko czeka na ciebie w Opanuj JavaScript!

Dowiedz sie więcej
App screenshot