9 styczeń 2020

Ciemny/Jasny motyw na stronie

Ciemny/Jasny motyw na stronie
Coraz więcej programów ale również i stron posiada motyw ciemny. Nie będę tutaj się zajmował tym czy i dlaczego używać ciemnego motywu. Takie informacje Sami jesteście wstanie znaleźć w internecie.
Dla mnie ciemny motyw jest idealny do pracy, mniej męczy oczy, jest też część techniczna bo ciemny motyw na urządzeniach OLED czy AMOLED nie zużywa energii itd.
Tak naprawdę to nie jest istotne czy strona jest jasna czy ciemna ale to jaki jest kontrast między elementami tło/tekst.

Przejdźmy do konkretów. Strona będzie posiadała przełącznik między ciemnym/jasnym motywem. Dane będą zapisywane w localStorage, a to po to aby po odświeżeniu strony nasz wybór nadal był aktualny. Oczywiście nic nie stoi na przeszkodzie aby gdzie indziej zapisywać te informacje.

Nie chcemy aby po wybraniu ciemnego motywu i po przejściu na inną stronę motyw z powrotem zmienił się na jasny. Domyślnie strona jest w motywie jasnym, dopiero kliknięcie na przełącznik zmienia nam motyw.

Najpierw porcja prostego html. Zaczniemy od przełącznika.

<div class="theme-switch-wrapper">
  <label class="theme-switch" for="checkbox">
    <input type="checkbox" id="checkbox" />
    <div class="slider round"></div>
  </label>
</div>

Zaś css oraz miejsce jego umieszczenie prezentuje poniższy css:

.theme-switch {
  display: inline-block;
  height: 34px;
  position: relative;
  width: 60px;
}

.theme-switch input {
  display: none;
}

.theme-switch-wrapper {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  margin-top: 20px;
}

.theme-switch-wrapper em {
  margin-left: 10px;
  font-size: 1rem;
}

.slider {
  background-color: #ccc;
  bottom: 0;
  cursor: pointer;
  left: 0;
  position: absolute;
  right: 0;
  top: 0;
  transition: 0.4s;
}

.slider:before {
  background-color: #fff;
  bottom: 4px;
  content: "";
  height: 26px;
  left: 4px;
  position: absolute;
  transition: 0.4s;
  width: 26px;
}

.slider.round {
  border-radius: 34px;
}

.slider.round:before {
  border-radius: 50%;
}

input:checked + .slider {
  background-color: #66bb6a;
}

input:checked + .slider:before {
  transform: translateX(26px);
}

Nasze style oprzemy o zmienne var.

:root {
  --primary-color: #24242b;
  --secondary-color: #433055;
  --font-color: #3f3f3f;
  --bg-color: #f3f3f3;
  --heading-color: #2a2a23;
}

html[data-theme="dark"] {
  --primary-color: #f89898;
  --secondary-color: #8894b3;
  --font-color: #d8d8f6;
  --bg-color: #242424;
  --heading-color: #818cab;
}

Pierwsze część czyli :root jest to styl wersji jasnej.
Druga cześć html[data-theme="dark"] odpowiada za wersję ciemną. data-theme="dark" jest dodawane to html za pomocą javascript po kliknięciu w przełącznik.

Reszta stylii odpowiedzialna za to jak będą zmieniać się kolory, background, kolor czcionki, h1, itd:

body {
  font-family: "Lato", sans-serif;
  background-color: var(--bg-color);
  color: var(--font-color);
  max-width: 900px;
  margin: 0 auto;
  padding: 0 30px;
  font-size: calc(1rem + 0.25vh);
}

h1 {
  color: var(--heading-color);
  font-family: "Lato", serif;
  font-size: 3rem;
  margin-bottom: 1vh;
}

p {
  font-size: 1.1rem;
  line-height: 1.6rem;
}

a {
  color: var(--primary-color);
  text-decoration: none;
  border-bottom: 3px solid transparent;
  padding-bottom: 5px;
  transition: border-bottom 1s;
  font-weight: bold;
}
a:hover,
a:focus {
  border-bottom: 3px solid currentColor;
}

section {
  margin: 0 auto;
}

.post-meta {
  font-size: 1rem;
  font-style: italic;
  display: block;
  margin-bottom: 4vh;
  color: var(--secondary-color);
}

Teraz przyszedł czas na javascript, jest on bardzo prosty.

const toggleSwitch = document.querySelector('.theme-switch input[type="checkbox"]');
// pobieramy z localStorage theme rodzaj dark/light
const currentTheme = localStorage.getItem('theme');

// jeżeli theme istnieje 
if (currentTheme) {
  // ustawiamy data-theme klasę w elemencie html data-theme="light"
  document.documentElement.setAttribute('data-theme', currentTheme);

  // jeżeli theme jest dark zmieniamy przełącznik a dokładnie checkbox na true
  if (currentTheme === 'dark') {
    toggleSwitch.checked = true;
  }
}

// funkcja ustawiająca data-theme w zależności od przełącznika
function switchTheme(event) {
  if (event.target.checked) {
    document.documentElement.setAttribute('data-theme', 'dark');
    localStorage.setItem('theme', 'dark');
  }
  else {
    document.documentElement.setAttribute('data-theme', 'light');
    localStorage.setItem('theme', 'light');
  }
}

// obserwujemy zdarzenie w naszym przypadku na change uruchamiamy funkcję switchTheme
toggleSwitch.addEventListener('change', switchTheme, false);

To chyba tyle, poniżej cały działający przykład.

See the Pen
Dark / Light theme on the page
by Greg (@Tomik23)
on CodePen.

UPDATE ---

Poniżej opisałem drugi sposób, inny a czy lepszy tego nie wiem. Sami oceńcie.

Najpierw potrzebujemy dwóch ikon sun oraz moon zawsze pobieram je z tego źródła icomoon.io Ikony svg zawsze optymalizuję poprzez tą stronę svgomg
Zawsze tworzę również zestaw ikon jako osobny zestaw symboli, które dodaję zaraz za otwarciem body.

<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
   <symbol id="sun-icon" viewBox="0 0 32 32">
     <path d="M16 9c-3.859 0-7 3.141-7 7s3.141 7 7 7 7-3.141 7-7-3.141-7-7-7zm0 12c-2.762 0-5-2.238-5-5s2.238-5 5-5 5 2.238 5 5-2.238 5-5 5zm0-14a1 1 0 001-1V4a1 1 0 00-2 0v2a1 1 0 001 1zm0 18a1 1 0 00-1 1v2a1 1 0 002 0v-2a1 1 0 00-1-1zm7.777-15.365l1.414-1.414a.999.999 0 10-1.414-1.414l-1.414 1.414a.999.999 0 101.414 1.414zM8.223 22.365l-1.414 1.414a.999.999 0 101.414 1.414l1.414-1.414a.999.999 0 10-1.414-1.414zM7 16a1 1 0 00-1-1H4a1 1 0 000 2h2a1 1 0 001-1zm21-1h-2a1 1 0 000 2h2a1 1 0 000-2zM8.221 9.635a.999.999 0 101.414-1.414L8.221 6.807a.999.999 0 10-1.414 1.414l1.414 1.414zm15.558 12.728a.999.999 0 10-1.414 1.414l1.414 1.414a.999.999 0 101.414-1.414l-1.414-1.414z"></path>
   </symbol>
   <symbol id="moon-icon" viewBox="0 0 32 32">
     <path d="M21.866 21.447c-3.117 3.12-8.193 3.12-11.313 0s-3.12-8.195 0-11.314a7.952 7.952 0 012.989-1.863.998.998 0 011.039.237.995.995 0 01.237 1.039c-.784 2.211-.25 4.604 1.391 6.245 1.638 1.639 4.031 2.172 6.245 1.391a.996.996 0 011.039.237.995.995 0 01.236 1.039 7.907 7.907 0 01-1.863 2.989zm-9.899-9.9a6.007 6.007 0 000 8.486c2.5 2.501 6.758 2.276 8.937-.51-2.247.141-4.461-.671-6.109-2.318s-2.458-3.861-2.318-6.108c-.18.141-.35.29-.51.451z"></path>
   </symbol>
</svg>

Do strony dodajemy mały skrypt zaraz za zestawem ikon, ten mały kod zapobiega powolnemu ładowaniu się motywu.

<script>
  try { 
    document.body.dataset.theme = localStorage.getItem('theme')
  } catch (e) {
    document.body.dataset.theme = null
  }
</script>

Teraz dodajemy html do strony, tworzymy button a w nim umieszczamy svg.

<button class="theme" aria-label="zmień motyw">
  <svg class="theme-light">
    <use xlink:href="#sun-icon"></use>
  </svg>
  <svg class="theme-dark">
    <use xlink:href="#moon-icon"></use>
  </svg>
</button>

Przydało by się również kilka styli aby odpowiednio ostylować button i svg. W stylach tutaj zawartych może czegoś brakować. Sami musicie ostylować odpowiednie i dopasować do własnej strony.
Na podstawie tego jaki jest ustawiony atrbut w data-them pokazujemy lub ukrywamy odpowiedni div z svg.

:root {
  --font-color:#202124;
}

body[data-theme=dark] {
  --font-color:#d8d8f6;
}

body:not([data-theme=dark]) .theme-dark {
  display: none
}

body[data-theme=dark] .theme-dark {
  fill: #fff
}

body[data-theme=dark] .theme-light {
  display: none
}

svg:not(:root) {
  overflow: hidden;
}

.theme {
  display: flex;
  align-items: center;
  cursor: pointer;
  border: none;
  padding: 0;
  background: transparent;
}

.theme svg {
  width: 40px;
  height: 40px;
}

.theme svg {
  width: 40px;
  height: 40px;
}

.theme::before {
  position: absolute;
  content: "przełącz";
  left: -55px;
  font-size: 0.8rem;
  color: var(--font-color);
}

Teraz najważniejsza cześć, zmieniamy loacalStorage w po każdym kliknięciu.

const toggleSwitch = document.querySelector('.theme');

toggleSwitch.addEventListener('click', () => {
  const currentTheme = localStorage.getItem('theme');

  // ustawiamy data-theme
  const typeTheme = currentTheme === 'dark' ? 'light' : 'dark';
  document.body.setAttribute('data-theme', typeTheme);

  // dodajemy rodzaj motywu do localStorage
  localStorage.setItem('theme', typeTheme);
});

Podsumowując jest to o wiele prostszy sposób zmiany motywów, do tego bardziej zgodny z ARIA
Tak naprawdę modyfikujemy tylko atribue, który jest ustawiany w body - data-theme. Produkcyjne działanie umieściłem na tej stronie