Mini sass grid

.row {
    display: flex;
    flex-wrap: wrap;
}

$columns: 12;

@for $i from 1 through $columns {
    .col-#{$i} {
        flex: 0 0 100% / $columns * $i;
    }
    .col-offset-#{$i} {
        margin-left: 100% / $columns * $i;
    }
}

// Responsive Breakpoints
[class^="col-"] {
    // Tablets
    @media (max-width: 768px) {
        flex-basis: 50%;
    }
    // Mobiles
    @media (max-width: 480px) {
        flex-basis: 100%;
    }
}

Docker + wordpress + phpmyadmin wersja Windows

Nie opiszę tutaj wszystkiego związanego z dockerem bo jestem świerzynka w tym temacie oraz jest tego tyle że po prostu nie da się tego opisać bez przepisywania dokumentacji 😉
Będzie to tylko mały wycinek tego co jest mi potrzebne do frontentu z przykładem użycia WordPress, MySQL oraz phpMyAdmin na Windowsie.

Do czego można by użyć Dockera?

Powiedźmy że chcemy stworzyć aplikację składającą się właśnie z WordPress + MySQL + phpMyAdmin aby przygotować nowy theme czy plugin.
Wszystko to musimy zainstalować osobno na komputerze do tego PHP + apache2 lub użyć Xampp’a, ale co jeśli musielibyśmy użyć kilku wersji PHP 5.5 czy 5.6 i do tego MySQL również w kilku wersjach.

Normalnie czekało by nas bardzo długie konfigurowanie każdej instancji, odinstalowanie elementów i instalacja nowych oraz ich konfiguracja czyli jednym słowem koszmar.

W takich przypadkach pomocny jest właśnie Docker. Wystarczy wybrać kontener z wersją aplikacji i już. Można nawet uruchomić wiele kontenerów równolegle, dzięki czemu możemy testować aplikację w kilku rożnych wersjach w tym samym czasie!
Najważniejsze przynajmniej dla mnie jest to że nie zaśmiecamy sobie środowiska zbędnymi instalacjami, a jak wiemy w tym względzie Windows jest dość ułomnym środowiskiem i można go łatwo „zepsuć”.

Teraz trochę definicji.

Docker jest narzędziem , które pozwala umieścić program oraz jego zależności w przenośnym wirtualnym kontenerze. Umożliwia utworzenie kontenerów zawierających pojedyncze aplikacje ale także łączyć je w większe środowiska.

Ok zaczynamy zabawę.

Najpierw należy zainstalować Dockera na komputerze cały proces jest opisany na zerknij tutaj
W dalszej części będziemy korzystać z narzędzia Docker Compose ono jest dla wersji MAC jak i Windows jest zaszyte w instalacji Docker zaś linuxowcy muszą je osobno doinstalować.

Otwieramy konsole i wpisujemy

docker run hello-world

Jeżeli wszystko przebiegło poprawnie powinniśmy zobaczyć w konsoli to co poniżej.

Sprawdźmy jakie mamy obrazy, powinien być jeden hello-world, aby to sprawdzić piszemy

docker images

i dostajemy

Może co poważniejszego zainstalujmy na przykład nginx

docker container run -it -p 80:80 nginx

Kilka chwil i mamy uruchomioną instancję nginx, otwieramy przeglądarkę i http://localhost/index.html i mamy powitalną stronę

Teraz wyobraźcie sobie ile czasu by to zajęło w konwencjonalnym podejściu instalacja tego pod Windosem 🙂

Kilka przydatnych komend poniżej.

  • docker – wyświetla wszystkie podstawowe komendy
  • docker help – podstawowa komenda dzięki której można sprawdzić składnie oraz wyszukać inne komendy
  • docker pull [nazwaObrazu] – pobranie obrazu
  • docker search [imageName:imageTag] – komenda wyszukuje obrazy o podanej nazwie i tagu
  • docker run [args] [imageName:imageTag/imageId] – stworzenie oraz uruchomienie kontenera z obrazu
  • -it – tryb interaktywny (możemy np: korzystać z bash’a)
  • -p – protokół mapuje wystawiony port naszego kontenera na port naszego localhost
  • -d – tryb deamon, działa na zasadzie usługi
  • -expose – pozwala nam wystawić post naszego kontenera na zewnątrz
  • docker ps – komenda pokazuje nam działające kontenery jeżeli chcemy zobaczyć również te niedziałające docker ps -a
  • docker kill/stop [containerName/containerId] – komenda służy do wyłączenia działającego kontenera. Zazwyczaj korzysta się z containerId z 3 pierwszych znaków
  • docker docker rename [containerOldName] [containerNewName] – komenda służy do zmiany nazwy kontenera
  • docker rm [containerName/containerId] – usunięcie kontenera z naszego komputera o zadanej nazwie lub id
  • docker rmi [imageName:imageTag/imageId] – usuniecie z naszego komputera obrazu o podanej nazwie i tagu lub id

To tylko ułamek wszystkich flag więcej można znaleźć tutaj.

Przejdźmy do konkretów.

A mianowicie chce uruchomić tak jak na wstępie pisałem WordPress + MySQL + phpMyaAmin i swój własny theme i tutaj przyda nam się Docker Compose.
Najpierw usuńmy pozostałości po naszych poprzednich próbach. Zobaczmy co jest zainstalowane najpierw

docker images -a

później

docker rm containerId

– czyli pierwsze 3 znaki kontenera i enter.

Zaczynamy od stworzenia folderu

mkdir docker-setup

następnie

cd docker-setup

i uruchamiam visual studio code przez komendę code .

Do vsc instaluje plugin Docker poniżej


plugin pokazuję kontenery images oraz Docker Hub wystarczy kliknąć na wielorybka, trochę upraszcza nam pracę mniej pisania w konsoli 😉

Na początku tworzę w docker-setup .dockerignore praktycznie coś na styl gita

Następnie tworzę plik docker-compose.yml w nim będzie działać się cała magia 🙂

  • version – oznacza wersję Docker Compose
  • services – sekcja, w której definiujemy serwisy
  • mysql:5.7 – nazwa własna usługi
  • image – wskazujemy z jakiego obrazu ma być zbudowany kontener
  • build – wskazujemy ścieżkę do pliku Dockerfile naszego serwisu – w projekcie nie używamy
  • ports – definiujemy porty, które będą mapowane z konteneru
  • links – zależność pomiędzy kontenerami, db oraz WordPress
  • environment – zmienna środowiskowa dla danego kontenera

Aby uruchomić Docker Compose w naszym terminalu używamy komendy

docerk-compose up

w miejscu w którym mamy docker-compose.yml

Za pierwszym razem wszystkie zależności które są w yml są zaciągane – nasza baza danych jak i WordPress.
Poniżej screen z vsc oraz z chrome w którym mamy WordPress w wersji do zainstalowania 🙂

Dodajmy do tego całego naszego środowiska również phpMyAdmin

phpmyadmin:
 depends_on:
  - db
 image: phpmyadmin/phpmyadmin
 ports:
  - 8080:80
 environment:
  MYSQL_ROOT_PASSOWRD: test

Musimy najpierw zastopować wszystkie kontenery czy to przez „ctrl+c” lub z innej konsoli „docker-compose down” albo z vsc prawy przycisk na poszczególny kontener i Remove Container jak kto woli. Ja używam docker-compose down jest najpewniejszym sposobem zamknięcia uruchomionych kontenerów.

Po dodaniu phpMyAdmin uruchamiamy ponownie „docker-compose up”

Wszystko działa szybciej bo pobierany jest tylko obraz phpMyAdmin, aby sprawdzić czy działa wchodzimy na to co ostawiliśmy mianowicie http://localhost:8080
Podajemy „Użytkownika root Hasło test” tak jak podaliśmy w docker-compose.yml
I naszym oczom ukazuje się phpMyAdmin z utworzoną baza wp

Wracamy do okna http://localhost/wp-admin/install.php tam instalujemy jak zawsze i wypełniamy danymi ale ZARAZ ZARAZ!!! jeśli wyłączymy wszystkie kontenery to co dodaliśmy do WordPress a mianowicie strony, artykuły wszystko to nam zniknie!!! Nie no tak to nie możne być!

Dodajemy własne artykuły wszystko co jest nam potrzebne w WordPressa i za pomocą phpMyAdmin eksportujemy bazę do pliku db.sql (nazwa oczywiście może być inna) nie musimy tego robić możemy pobrać jakąś inną bazę danych z innego WordPress jeżeli takową posiadamy i wystarczy to przygotowania naszego theme.
Dump bazy umieszczam w folderze „data/db.sql” w naszym projekcie oraz dodaje odpowiedni wpis w docker-compose.yml

db:
 image: mysql:5.7
 volumes:
  - ./data:/docker-entrypoint-initdb.d
 restart: always
 ...

volumes pobiera nam db.sql za każdym razem gdy uruchamiamy „docker-compose up” i mamy z głowy uzupełnianie danych w bazie, możemy wejść na http://localhost i zobaczyć naszego WordPress

Dodajemy własne theme.

Teraz druga cześć chcemy dodać własny theme, ponownie stopujemy docker-compose down w głównym folderze naszego projektu dodaje folder wp-content/themes/newTemplate z plikami odpowiedzialnymi za theme.
Teraz aby to zostało zaciągnięte i widoczne w WordPressie należy dodać values tym razem do wpisu wordpress.

Ponownie docker-compose up, teraz możemy pracować na theme, aby zobaczyć zmiany które zrobiliśmy w theme wystarczy odświeżyć WordPress F5 w przeglądarce. Nie trzeba stopować kontenerów.

Źródła znajdują się pod tym adresem

Gulp workflow: minimalizacja zdjęć i konwersja na webp

Kiedyś w zamierzchłych czasach używałem perla + lib ImageMagick do konwersji zdjęć, ale mamy XXI wiek i Node 😉
Od jakiegoś czasu zmienił się mój workflow dotyczący obróbki zdjęć na potrzeby stron internetowych.
Do tej pory używałem gulp-image, gulp-imagemin i wiele innych, ale zawsze coś było nie tak, zawsze czegoś brakowało.
Ostatnio używam takiego zapisu jak poniżej

<picture>
    <source class="img-responsive" srcset="./images/thumbnail/IMG_5600.webp" type="image/webp">
    <source class="img-responsive" srcset="./images/thumbnail/IMG_5600.jpg" type="image/jpeg">
    <img class="img-responsive" src="./images/thumbnail/IMG_5600.jpg" alt="">
</picture>

i potrzebowałem również webp, no i co następna biblioteka gulp-webp jest potrzebna 🙁
Mały research i okazało się że jest biblioteka sharp, która bardzo dobrze zastępuje ImageMagick więcej na tej stronie. Biblioteka ta jest w stanie zastąpić wszystko co do tej poru używałem.

Obecnie korzystam z pluginu gulp-responsive który pod spodem używa tejże biblioteki sharp.
Zmiana rozmiaru obrazu za pomocą tej biblioteki jest zazwyczaj 4x-5x szybsza niż w przypadku najszybszych ustawień ImageMagick i GraphicsMagick, a więc czemu jej nie używać. Oczywiście ta bibliotek nie tylko do zmniejsza grafiki ale również przycina, zmieniania formatu, zapisu do grayscale czyli szarości i wiele innych użytecznych rzeczy potrafi 🙂

Dobra zaczynamy cały proces.
Oczywiście musimy mieć zainstalowane node w naszym środowisku ale to nie temat na ten artykuł.

Na początek tworzymy package.json

npm init -y

Instalujemy wszystkie potrzebne biblioteki

npm i -D gulp gulp-responsive gulp-load-plugins

Tworzymy folder ze zdjęciami „sources” następnie gulpfile.js

var gulp = require('gulp');
var $ = require('gulp-load-plugins')();

gulp.task('images', function () {
    return gulp.src(['sources/**/*.{jpg,png}'])
        .pipe($.responsive({
            '*.jpg': [{
                width: 1200,
                height: 800
            }, {
                // Konwertujemy jpg do formatu webp
                width: 1200,
                height: 800,
                rename: {
                    extname: '.webp'
                }
            }]
        }, {
                // globalne ustawienia
                max: true,
                quality: 65,
                progressive: true,
                withMetadata: false,
                errorOnEnlargement: true
            }))
        .pipe(gulp.dest('dist'));
});

Oczywiście aby ten wynalazek uruchomić należy tak jak wszystkie taski w gulpie czyli:

gulp images

Ustawienia globalne:
max parametr potrzebny gdy zdjęcie jest zrobione w pionie – trzyma proporcje
quality wiadomo jakość dla grafik jpg jak i webp
progressive, jpg ładuje się od góry do dołu linia po linii, progresywny ładuje się całościowo w „kiepskiej” jakości i jest stopniowo wyostrzany
withMetadata usuwane są wszystkie metadane z pliku
errorOnEnlargement pokazuje błędy jeżeli takowe się pojawią

Biblioteka jest bardzo szybka 137 zdjęć (ponad 550MB) w rozdzielczości 2700×1800 zajęło tej bibliotece 32s z ustawieniami globalnymi jak powyżej bez webp. Oczywiście szybkość zależy od hardwaru jaki posiadamy.

Więcej można przeczytać na githubie opisane są tam bardziej skomplikowane użycia tej biblioteki jak dostępne opcje konfiguracyjne.

Gulp ES6 z Babel

Do gulp instalujemy:

npm install babel-core babel-preset-es2015 --save-dev

W głównym folderze tworzymy plik:

.babelrc

W nim dodajemy zapis:

{
  "presets": [
    "es2015"
  ]
}

Aby zadziałał nam baabel z gulp należy zmodyfikować nazwę gulpfile.js na gulpfile.babel.js
Ja na wszelki wypadek duplikuję plik i zmieniam nazwę tak na wszelki wypadek i później dokonuję odpowiednich zmian w gulpfile.babel.js

'use strict';

import gulp from 'gulp';
import sass from 'gulp-sass';
import autoprefixer from 'gulp-autoprefixer';
import sourcemaps from 'gulp-sourcemaps';

const dir = {
  src: 'src',
  dest: 'build'
};

const sassPaths = {
  src: `${dir.src}/style.scss`,
  dest: `${dir.dest}/style/`
};

gulp.task('style', () => {
  return gulp.src(sassPaths.src)
    .pipe(sourcemaps.init())
    .pipe(sass.sync().on('error', plugins.sass.logError))
    .pipe(autoprefixer())
    .pipe(gulp.dest(sassPaths.dest));
});

...

Template strings w Javascript ES6, ES2015

Ile razy człowiek się męczył aby zwykły formularz ze zmiennymi zapisać w js 😉
Z pokonaniem tego problemu sięgamy po template string. Używamy tego znaku (`) znajduje się on nad tabulatorem z angielskiego backtick, po naszemu odwrócony apostrof a zmienne umieszczamy w takiej formie ${zmienna}
Wszystko razem można zapisać `${zmienna}`

A teraz małe przykłady.

Powiedzmy że mamy sobie obiekt.

var person = {
    name: 'Greg',
    job: 'front-end',
    city: 'Warszawa'
}

A html to:

var html =
'<div class="person">'+
    '<h1>' + person.name + '</h1>' +
    '<h2>' + person.job + ' ' + person.city + '</h2>' +
'</div>';

A nie prościej tak?:

const html = `
    <div class="person">
        <h1>${person.name}</h1>
        <h2>${person.job} ${person.city}</h2>
    </div>
`;

Oczywiście można te zmienne mnożyć, dodawać odejmować itd. 🙂

const x = 1;
const y = 2;
const result = `${ x + y }`;

console.log(result); // 1 + 2 = 3

Taki zapis jak pokazałem niesamowicie skraca nam pracę i mniej błędów popełniamy, ale jest jeden problem i to poważny a zwie się on IE (Internet Explorer) 🙂
Niestety nasz kod musi być jeszcze przez jakiś czas wstecz kompatybilny ze starymi przeglądarkami, dlatego w swojej pracy używam gulp, webpacka a czasami je również łączę.

Małe wytłumaczenie:
1. Gulp to, oparty o platformę Node.js, system do automatyzacji pracy. Głównym jego zadaniem jest więc zautomatyzowanie wielu czynności, jakie musi wykonać programista podczas swojej pracy. Przykład użycia – zerknij tutaj
2. Webpack to narzędzie, które rozwiązuje problem dzielenia kodu na moduły i pozwala łatwo zarządzać zależnościami występującymi między nimi.

Więcej poszukajcie sobie w google bo naprawdę warto znać te technologie.

Wracając do naszego problemu z IE i nie tylko, ja użyłem biblioteki gulp-bable
Najpierw instalacja z linii komend biblioteki gulp-babel

npm install --save-dev gulp-babel babel-core babel-preset-es2015

Teraz dodajemy do gulpfile.js naszą bibliotekę oraz task

const gulp = require('gulp');
const babel = require('gulp-babel');


gulp.task('js', function () {
    return gulp.src('src/**/*.js')
        .pipe(babel({
            "presets": ["es2015"]
        }))
        .pipe(gulp.dest('dist'));
});

Taki kod w pliku template.js który chcemy zmienić na es2015 umieszczamy w folderze src

const person = {
    name: 'Greg',
    job: 'front-end',
    city: 'Warszawa'
}

const html = `
    <div class="person">
        <h1>${person.name}</h1>
        <h2>${person.job} ${person.city}</h2>
    </div>
`;

console.log(html);

const x = 1;
const y = 2;
const result = `${ x + y }`;

console.log(result);

Po uruchomieniu taska js w folderze dist otrzymujemy js zmieniony na es205, wynik poniżej

'use strict';

var person = {
    name: 'Greg',
    job: 'front-end',
    city: 'Warszawa'
};

var html = '\n    <div class="person">\n        <h1>' + person.name + '</h1>\n        <h2>' + person.job + ' ' + person.city + '</h2>\n    </div>\n';

console.log(html);

var x = 1;
var y = 2;
var result = '' + (x + y);

console.log(result);

I ten kod załączamy do naszej strony będzie działać również ze starszymi przeglądarkami.

Oczywiście to tylko pobieżne poruszenie tego tematu, więcej w google 😉

Pasek postępu przewijania strony.

Dobrych praktyk UX nigdy za wiele. Warto czasami do strony dodać coś co nawet w najmniejszym stopniu zwiększy czas przebywania usera na stronie.

Najpierw pobieramy o ile przewinęliśmy stronę od lewego górnego rogu window.pageYOffset,
później pobieramy wysokość okna bez uwzględniania toolbarów i scrollbarów window.innerHeight, następnie pobieramy document.body.clientHeight czyli wysokość całego okna.

const ProgressBar = function () {

    window.addEventListener('scroll', () => {
        let ws = window.pageYOffset,
            wh = window.innerHeight,
            dh = document.body.clientHeight;
        let scrollPercent = (ws / (dh-wh)) * 100;
        const progressBar = document.querySelector('#progress');
        if(ws > 50) {
            progressBar.setAttribute('style','width:'+ scrollPercent + '%');
        } else {
            progressBar.removeAttribute('style');
        }
    });

}();

Jeżeli przewiniemy o więcej niż 50px wtedy do id progress dodajemy style width, jeżeli jest mniejsze to usuwamy atrybut style z id.

Dodajemy też css aby to zadziałało.

.bar {
    position: fixed;
    background: rgb(255, 0, 0);
    height: 10px;
    width: 0;
    top: 0;
}

Działający przykład można znaleźć tutaj – działający przykład

Rozwijane menu

Najpierw budujemy strukturę html. Wszystko jest robione w scss, js w wersji ES6 i gulp
Do ES6 używam gulp-babel aby przekonwertować js do es2015

<div class="container">

    <div id="accorions">
        <div class="accordion">Section 1</div>
        <div class="panel">
            <p>Sauerkraut can be garnished with cored avocado, also try decorateing the cake with hollandaise sauce.
                Asparagus can be marinateed with crushed lettuce, also try flavoring the loaf with salsa verde.
                with lobsters drink cream.</p>
        </div>

        <div class="accordion">Section 2</div>
        <div class="panel">
            <p>Aww, raid me parrot, ye dead landlubber! Swashbuckling, coal-black whales cowardly vandalize a mighty, gutless scabbard.
                Scabbards sing on booty at port degas! Small, dead tobaccos darkly fight a black, lively dagger.
                seashells fall with love.</p>
        </div>

        <div class="accordion">Section 3</div>
        <div class="panel">
            <p>Turbulence at the bridge was the adventure of mineral, lowered to a gravimetric mermaid.
                Moon at the universe was the mystery of coordinates, feeded to a real planet.
                wisely handle a processor.</p>
        </div>
    </div>

</div>

Dodajemy trochę css, na początku importuję normalize

@import "normalize";

.container {
    max-width: 500px;
    margin: 200px auto;

}

.accordion {
    background-color: #eee;
    color: #444;
    cursor: pointer;
    padding: 18px;
    width: 100%;
    border: none;
    text-align: left;
    outline: none;
    font-size: 15px;
    transition: 0.4s;
    &:after {
        content: '\002B';
        color: #777;
        font-weight: bold;
        float: left;
        margin-right: 5px;
    }
    &:hover {
        background-color: #ddd;
    }
    &.active {
        background-color: #ddd;
        &:after {
            content: "\2212";
        }
    }
}

.panel {
    padding: 0 18px;
    background-color: white;
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.2s ease-out;
    li {
        list-style: none;
    }
}

Teraz najważniejsza część, dodajemy js

"use strict";

class Accordion {

    constructor(options) {
        this.options = options;
    }

    creatAccordion() {
        let acc = this.getAccordionName();

        [...acc].forEach((el, i) => {
            el.addEventListener('click', el => {
                if(this.options.type === true && !this.hasClass(el, this.options.activeName)) {                
                    this.removeActiveAndPanelHeight();
                }
                el.target.classList.toggle(this.options.activeName);
                let panel = el.target.nextElementSibling;
                if (panel.style.maxHeight) {
                    panel.style.maxHeight = null;
                } else {
                    panel.style.maxHeight = panel.scrollHeight + 'px';
                }
            })
        })

    }

    getAccordionName() {
        return document.getElementsByClassName(this.options.accordionName);
    }

    hasClass(element, cla) {
        return (' ' + element.className + ' ').indexOf(' ' + cla + ' ') > -1;
    }

    removeActiveAndPanelHeight() {
        const currentActive = document.querySelectorAll("." + this.options.activeName);
        const heightPanel = document.querySelectorAll("." + this.options.panelName);

        [...currentActive].forEach(ca => {
            ca.classList.remove(this.options.activeName);
        });

        [...heightPanel].forEach(hp => {
            hp.removeAttribute("style");
        });
    }
}

const options = {
    'accordionName': 'accordion',
    'activeName': 'active',
    'panelName': 'panel',
    'type': true
};

const accordion = new Accordion(options);
accordion.creatAccordion();

Przykład można zobaczyć pod tym adresem

Ostatni parametr w options czyli ‚type’: true lub false umożliwia sterowaniem zwijania menu, true powoduje że każde kliknięcie na menu zwija inne rozwinięte zaś false pozostawia rozwinięte każde kliknięcie.
Oczywiście options można przenieść do html aby z tego miejsca sterować nie musi być ten obiekt umieszczony w js.

Całość włącznie z gulpem można zobaczyć na github

Nowy sposób serwowania ikon SVG

Sposób ten działa nawet w starszych przeglądarkach takich jak IE9.
Jednym z pierwszych możliwości jest budowa zestawu ikon które umieszczam od razu za „body” dla czego tutaj a nie na dole strony przed zamknięciem „/body” otóż gdy strona jest zbyt złożona i renderuje się dość wolno lub otwieramy stronę na telefonie i to w dodatku na słabym internecie np. 2G to ikony nie są widoczne od razu tylko renderują się dopiero po jakimś czasie. Sprawdzone i przetestowane.

<svg xmlns="http://www.w3.org/2000/svg" style="width: 0; height: 0; display: none;">
<symbol id="icon-home" viewBox="0 0 32 32">
<title>home</title>
<path d="M32 18.451l-16-12.42-16 12.42v-5.064l16-12.42 16 12.42zM28 18v12h-8v-8h-8v8h-8v-12l12-9z"></path>
</symbol>
<symbol id="icon-pencil" viewBox="0 0 32 32">
<title>pencil</title>
<path d="M27 0c2.761 0 5 2.239 5 5 0 1.126-0.372 2.164-1 3l-2 2-7-7 2-2c0.836-0.628 1.874-1 3-1zM2 23l-2 9 9-2 18.5-18.5-7-7-18.5 18.5zM22.362 11.362l-14 14-1.724-1.724 14-14 1.724 1.724z"></path>
</symbol>
</svg>

Teraz w miejscu w którym chcemy umieścić ikonę

<svg class="icon icon-home">
   <use xlink:href="#icon-home"></use>
</svg>

Oczywiście nie obejdzie się bez dodania odpowiedniego stylu dla icon 🙂

.icon {
  display: inline-block;
  width: 1em;
  height: 1em;
  stroke-width: 0;
  stroke: currentColor;
  fill: currentColor;
}

Oczywiście ikony można kolorować dodając fill dla konkretnej ikony

.icon-home {
  fill: red;
}

Innym sposobem jest umieszczenie ikon w osobnym pliku np. symbol.svg
Do ikon dostajemy się w ten sposób symbol.svg#icon-home
Pełny przykład poniżej.

<svg class="icon icon-home">
   <use xlink:href="symbol.svg#icon-home"></use>
</svg>

Ale żeby to wszystko zagrało w IE musimy sięgnąć po js – svgxuse.js
Js ten dodajemy na końcu strony przed zamknięciem „/body”

Polecam do budowania systemu icon a przynajmniej na początku żeby zobaczyć z czym to się „je” stronę icomoon.io system ikon

Tam można wybrać interesujące nas ikony, a na dole na belce jest Generate SVG & More. Po kliknięciu tego napisu zapisuje się nasz wybrany zestaw icon powiedzmy na pulpicie, rozpakowujemy.

Często gdy dodaje własne ikony czy to z ilustratora czy innego programu do wektorów to najpierw optymalizuję ikony bo zawsze jest tak że te ikony które są zapisywane przez te programy mają nadmiar kodu. Dobrym przykładem takiej strony jest SVG optimizer
Czysty kod doklejam później do svg jako następny symbol.

Obrót strony o losową liczbę stopni

PHP:

$rotate_deg = rand(2, 4);
if(time()%3==0) $rotate_deg=180;
$smarty->assign('rotate_deg', $rotate_deg);

Smarty, html i css:

!{if $smarty.now%2==0 && $now_date=='04-01'}!
<style>body {overflow-x: hidden;}</style>
<div style="position: fixed; bottom:10px; left:10px; font-size:15px; color:green;">;)</div>
<div style="transform:rotate(!{$rotate_deg}!deg); -webkit-transform:rotate(!{$rotate_deg}!deg); -moz-transform:rotate(!{$rotate_deg}!deg); -o-transform:rotate(!{$rotate_deg}!deg);">
!{else}!
<div>
!{/if}!
..... strona ....
</div>

Jak przyspieszyć WordPress

Poniżej kilka trików które pozwoliły mi przyspieszyć bloga z 32/22 w PageSpeed Insigth do 90/100 – oczywiście to zależy od strony, ilości zdjęć, szybkości serwera itp, wszystko ma znaczenie.
I to jest blog ze zdjęciami (blog.grzegorztomicki.pl), jak dla mnie bardzo zacnie 🙂

1. Będziemy potrzebować dostęp do .htaccess
W którym dodajemy (nie będę opisywał co dokładnie jest w nim robione bo wszystko można znaleźć opisane w internecie)

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
#</IfModule>



<IfModule mod_deflate.c>
  # Compress HTML, CSS, JavaScript, Text, XML and fonts
  AddOutputFilterByType DEFLATE application/javascript
  AddOutputFilterByType DEFLATE application/rss+xml
  AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
  AddOutputFilterByType DEFLATE application/x-font
  AddOutputFilterByType DEFLATE application/x-font-opentype
  AddOutputFilterByType DEFLATE application/x-font-otf
  AddOutputFilterByType DEFLATE application/x-font-truetype
  AddOutputFilterByType DEFLATE application/x-font-ttf
  AddOutputFilterByType DEFLATE application/x-javascript
  AddOutputFilterByType DEFLATE application/xhtml+xml
  AddOutputFilterByType DEFLATE application/xml
  AddOutputFilterByType DEFLATE font/opentype
  AddOutputFilterByType DEFLATE font/otf
  AddOutputFilterByType DEFLATE font/ttf
  AddOutputFilterByType DEFLATE image/svg+xml
  AddOutputFilterByType DEFLATE image/x-icon
  AddOutputFilterByType DEFLATE text/css
  AddOutputFilterByType DEFLATE text/html
  AddOutputFilterByType DEFLATE text/javascript
  AddOutputFilterByType DEFLATE text/plain
  AddOutputFilterByType DEFLATE text/xml

  # Remove browser bugs (only needed for really old browsers)
  BrowserMatch ^Mozilla/4 gzip-only-text/html
  BrowserMatch ^Mozilla/4\.0[678] no-gzip
  BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
  Header append Vary User-Agent
</IfModule>

<IfModule mod_expires.c>
  ExpiresActive on

# Perhaps better to whitelist expires rules? Perhaps.
  ExpiresDefault                          "access plus 1 month"

# cache.appcache needs re-requests in FF 3.6 (thx Remy ~Introducing HTML5)
  ExpiresByType text/cache-manifest       "access plus 0 seconds"



# Your document html
  ExpiresByType text/html                 "access plus 0 seconds"

# Data
  ExpiresByType text/xml                  "access plus 0 seconds"
  ExpiresByType application/xml           "access plus 0 seconds"
  ExpiresByType application/json          "access plus 0 seconds"

# RSS feed
  ExpiresByType application/rss+xml       "access plus 1 hour"

# Favicon (cannot be renamed)
  ExpiresByType image/x-icon              "access plus 1 week"

# Media: images, video, audio
  ExpiresByType image/gif                 "access plus 1 month"
  ExpiresByType image/png                 "access plus 1 month"
  ExpiresByType image/jpg                 "access plus 1 month"
  ExpiresByType image/jpeg                "access plus 1 month"
  ExpiresByType video/ogg                 "access plus 1 month"
  ExpiresByType audio/ogg                 "access plus 1 month"
  ExpiresByType video/mp4                 "access plus 1 month"
  ExpiresByType video/webm                "access plus 1 month"

# HTC files  (css3pie)
  ExpiresByType text/x-component          "access plus 1 month"

# Webfonts
  ExpiresByType font/truetype             "access plus 1 month"
  ExpiresByType font/opentype             "access plus 1 month"
  ExpiresByType application/x-font-woff   "access plus 1 month"
  ExpiresByType image/svg+xml             "access plus 1 month"
  ExpiresByType application/vnd.ms-fontobject "access plus 1 month"

# CSS and JavaScript
  ExpiresByType text/css                  "access plus 1 year"
  ExpiresByType application/javascript    "access plus 1 year"
  ExpiresByType text/javascript           "access plus 1 year"

  <IfModule mod_headers.c>
    Header append Cache-Control "public"
    Header unset ETag
    FileETag None
  </IfModule>

</IfModule>

2. Instalujemy „BJ Lazy Load”
Jest to plugin do „wolnego” ładowania się obrazków więcej na https://wordpress.org/plugins/bj-lazy-load/

3. Instalujemy „WP Super Cache”
Włączamy w nim w zakładce „Easy” na Caching on (Recommended) – więcej na https://wordpress.org/plugins/wp-super-cache/, czy działa można sprawdzić na stronie na samym dole powinien pojawić się mniej więcej taki wpis.

<!--HTML compressed, size saved 9.28%. From 64384 bytes, now 58410 bytes-->
<!-- Dynamic page generated in 0.974 seconds. -->
<!-- Cached page generated by WP-Super-Cache on 2016-01-01 11:42:46 -->


4. Najtrudniejsza część instalujemy „Above The Fold Optimization”

Nie włączamy go.
Klikamy na zakładkę „Extract Full CSS” pojawia się lista wszystkich css które są na stronie oraz textarea z połączonymi wszystkimi plikami css „Full CSS” – textarea.

Kopiuję Ctrl + C do strony https://jonassebastianohlsson.com/criticalpathcssgenerator/

Na tej stronie w pole:
1. URL podaję adres blog lub specyficznej strony na naszym blogu,
2. W „FULL CSS” wklejam cały nasz wygenerowany css.
3. Klikamy „Create Critical Path CSS”, po dłuższej chwili (w zależności od tego jak duży jest css w moim wypadku wszystkie połączone cssy są wielkości ponad 0,5MB) pojawia się okno textarea „CRITICAL PATH CSS” a w nim dostosowane css które kopiuję do pliku header.php od razu pod ,
oczywiście można to zrobić ładniej ale że jestem lazy to mi się nie chciało 🙂

Uwaga z tego skróconego css usuwamy wszystkie @font-face eot,ttf itd czcionki.

5. Wygląd -> Edytor -> otwieramy Funkcje motywu (functions.php)
Na samym końcu dodaję:

<?php
function remove_head_scripts() {
   remove_action('wp_head', 'wp_print_scripts');
   remove_action('wp_head', 'wp_print_head_scripts', 9);
   remove_action('wp_head', 'wp_enqueue_scripts', 1);

   remove_action( 'wp_head', 'rsd_link');
   remove_action( 'wp_head', 'wlwmanifest_link');
   remove_action( 'wp_head', 'wp_generator');
   remove_action( 'wp_head', 'start_post_rel_link');
   remove_action( 'wp_head', 'index_rel_link');
   remove_action( 'wp_head', 'adjacent_posts_rel_link');
   remove_action( 'wp_head', 'wp_shortlink_wp_head');

   add_action('wp_footer', 'wp_print_scripts', 5);
   add_action('wp_footer', 'wp_enqueue_scripts', 5);
   add_action('wp_footer', 'wp_print_head_scripts', 5);
}
add_action( 'wp_enqueue_scripts', 'remove_head_scripts' );

?>

Powyższa funkcja przenosi cały script do sekcji footer

Dodaję również jedną przydatną rzecz czyli kompresję html – usuwanie zbędnych spacji, nowych linii itd, również dodajemy w function.php na samym dole

<?php
class WP_HTML_Compression
{
    // Settings
    protected $compress_css = true;
    protected $compress_js = true;
    protected $info_comment = true;
    protected $remove_comments = true;

    // Variables
    protected $html;
    public function __construct($html)
    {
     if (!empty($html))
     {
         $this->parseHTML($html);
     }
    }
    public function __toString()
    {
     return $this->html;
    }
    protected function bottomComment($raw, $compressed)
    {
     $raw = strlen($raw);
     $compressed = strlen($compressed);
     
     $savings = ($raw-$compressed) / $raw * 100;
     
     $savings = round($savings, 2);
     
     return '<!--HTML compressed, size saved '.$savings.'%. From '.$raw.' bytes, now '.$compressed.' bytes-->';
    }
    protected function minifyHTML($html)
    {
     $pattern = '/<(?<script>script).*?<\/script\s*>|<(?<style>style).*?<\/style\s*>|<!(?<comment>--).*?-->|<(?<tag>[\/\w.:-]*)(?:".*?"|\'.*?\'|[^\'">]+)*>|(?<text>((<[^!\/\w.:-])?[^<]*)+)|/si';
     preg_match_all($pattern, $html, $matches, PREG_SET_ORDER);
     $overriding = false;
     $raw_tag = false;
     // Variable reused for output
     $html = '';
     foreach ($matches as $token)
     {
         $tag = (isset($token['tag'])) ? strtolower($token['tag']) : null;
         
         $content = $token[0];
         
         if (is_null($tag))
         {
             if ( !empty($token['script']) )
             {
                 $strip = $this->compress_js;
             }
             else if ( !empty($token['style']) )
             {
                 $strip = $this->compress_css;
             }
             else if ($content == '<!--wp-html-compression no compression-->')
             {
                 $overriding = !$overriding;
                 
                 // Don't print the comment
                 continue;
             }
             else if ($this->remove_comments)
             {
                 if (!$overriding && $raw_tag != 'textarea')
                 {
                     // Remove any HTML comments, except MSIE conditional comments
                     $content = preg_replace('/<!--(?!\s*(?:\[if [^\]]+]|<!|>))(?:(?!-->).)*-->/s', '', $content);
                 }
             }
         }
         else
         {
             if ($tag == 'pre' || $tag == 'textarea')
             {
                 $raw_tag = $tag;
             }
             else if ($tag == '/pre' || $tag == '/textarea')
             {
                 $raw_tag = false;
             }
             else
             {
                 if ($raw_tag || $overriding)
                 {
                     $strip = false;
                 }
                 else
                 {
                     $strip = true;
                     
                     // Remove any empty attributes, except:
                     // action, alt, content, src
                     $content = preg_replace('/(\s+)(\w++(?<!\baction|\balt|\bcontent|\bsrc)="")/', '$1', $content);
                     
                     // Remove any space before the end of self-closing XHTML tags
                     // JavaScript excluded
                     $content = str_replace(' />', '/>', $content);
                 }
             }
         }
         
         if ($strip)
         {
             $content = $this->removeWhiteSpace($content);
         }
         
         $html .= $content;
     }
     
     return $html;
    }
     
    public function parseHTML($html)
    {
     $this->html = $this->minifyHTML($html);
     
     if ($this->info_comment)
     {
         $this->html .= "\n" . $this->bottomComment($html, $this->html);
     }
    }
   
    protected function removeWhiteSpace($str)
    {
     $str = str_replace("\t", ' ', $str);
     $str = str_replace("\n",  '', $str);
     $str = str_replace("\r",  '', $str);
     
     while (stristr($str, '  '))
     {
         $str = str_replace('  ', ' ', $str);
     }
     
     return $str;
    }
}

function wp_html_compression_finish($html)
{
    return new WP_HTML_Compression($html);
}

function wp_html_compression_start()
{
    ob_start('wp_html_compression_finish');
}
add_action('get_header', 'wp_html_compression_start');
?>

6. Najważniejsza część pracy nad przyspieszeniem strony to oczywiście modyfikacja zdjęć.
Zdjęcia powinny mieć kompresję ustawioną na 70%, usunięcie wszystkich metadanych ze zdjęć, powinny być zapisane jako progressive, wielkość dostosowana do wielkości miejsca w którym się pojawiają zdjęcia.

7. W przyszłości zdjęcie przerobię na nowe rozwiązania dla wersji mobilnych
Więcej na https://scottjehl.github.io/picturefill/examples/demo-01.html oraz https://ericportis.com/posts/2014/srcset-sizes/

Tego rozwiązania używa między innymi theguardian.com a to wygląda mniej więcej tak (to jest jeden plik tylko że w różnych wielkościach):

<picture>
<!--[if IE 9]><video style="display: none;"><![endif]-->
<source media="(min-width: 1300px)" sizes="1300px" srcset="https://i.guim.co.uk/img/media/2fa72163af4712211cbf9abd929cc377d4af267b/0_0_4928_2957/master/4928.jpg?w=1300&amp;q=85&amp;auto=format&amp;sharp=10&amp;s=10e2200031125f835392892e9f813634 1300w">
<source media="(min-width: 1140px)" sizes="1140px" srcset="https://i.guim.co.uk/img/media/2fa72163af4712211cbf9abd929cc377d4af267b/0_0_4928_2957/master/4928.jpg?w=1140&amp;q=85&amp;auto=format&amp;sharp=10&amp;s=13d6ffcf420fb95bff93b5ca7a418f8e 1140w">
<source media="(min-width: 980px)" sizes="1125px" srcset="https://i.guim.co.uk/img/media/2fa72163af4712211cbf9abd929cc377d4af267b/0_0_4928_2957/master/4928.jpg?w=1125&amp;q=85&amp;auto=format&amp;sharp=10&amp;s=63fa2db8b2dccfc0ab408e25551c32e6 1125w">
<source media="(min-width: 740px)" sizes="965px" srcset="https://i.guim.co.uk/img/media/2fa72163af4712211cbf9abd929cc377d4af267b/0_0_4928_2957/master/4928.jpg?w=965&amp;q=85&amp;auto=format&amp;sharp=10&amp;s=c85b88b5fa49b0854485bd720d689155 965w">
<source media="(min-width: 660px)" sizes="725px" srcset="https://i.guim.co.uk/img/media/2fa72163af4712211cbf9abd929cc377d4af267b/0_0_4928_2957/master/4928.jpg?w=725&amp;q=85&amp;auto=format&amp;sharp=10&amp;s=048d9b371a22cebba3bb7dcd8e2d619c 725w">
<source media="(min-width: 480px)" sizes="645px" srcset="https://i.guim.co.uk/img/media/2fa72163af4712211cbf9abd929cc377d4af267b/0_0_4928_2957/master/4928.jpg?w=645&amp;q=85&amp;auto=format&amp;sharp=10&amp;s=97c169eb0122c0552b270a4be96e0410 645w">
<source media="(min-width: 0px)" sizes="465px" srcset="https://i.guim.co.uk/img/media/2fa72163af4712211cbf9abd929cc377d4af267b/0_0_4928_2957/master/4928.jpg?w=465&amp;q=85&amp;auto=format&amp;sharp=10&amp;s=b4476e761590a0a1175cb66c5bfd8a8d 465w">
<!--[if IE 9]></video><![endif]-->
<img class="maxed responsive-img" itemprop="contentUrl" alt="Francesca Chaouqui" src="https://i.guim.co.uk/img/media/2fa72163af4712211cbf9abd929cc377d4af267b/0_0_4928_2957/master/4928.jpg?w=300&amp;q=85&amp;auto=format&amp;sharp=10&amp;s=c69f163a8116e3ac4ad7cc8170a1ad11">
</picture>

A poniżej wynik mojej pracy.

desctop

mobile

W webpagetest też jest bardzo pozytywnie 🙂

webpage