Улучшаем юзабилити в вебе или создание Skeleton Screens с CSS

Улучшаем юзабилити в вебе или создание с помощью CSS

При создании веб приложений, зачастую уделяют недостаточное внимание проектированию экрана загрузки. Мнение пользователя о производительности складывается не только из объективных показателей быстрой загрузки и отображения, но и, как ни парадоксально, из ощущений и опыта получаемых пользователем при медленном интернет соединении. Ничего не поняли? Попробую объяснить более простым языком далее.

Я имею в виду, что если пользователь находится в каком нибудь далеком от цивилизации месте, куда ещё «не проникают добрые лучи скоростного интернета», то увидев бегунок загрузки и отрисовку Skeleton Screens он получит больше удовлетворения от приложения, чем если его взору предстанет девственно белая страница с надписью «обождите гружуся».

В то время как разработчики должны прикладывать максимум усилий для минимизации и кэширования, дизайнерам следует задуматься о том, как пользовательский интерфейс будет выглядеть и вести себя, в состояниях «loading» или «offline». 

Иллюзия скорости

Используя веб приложение, рядовой пользователь, избалованный нативными приложениями, применяет завышенные требования к производительности первых, не взирая на то, что он находится в Урюпинске и его смартфон с трудом ловит, в лучшем случае 3G, а то и вовсе Edge. 

Воспринимаемая производительность — это субъективная оценка пользователем скорости загрузки и отображения контента или другими словами это то, как пользователь ощущает производительность веб приложения. Идея состоит в том, что пользователи будут более терпеливы и будут считать, что приложение более быстрое, если они знают что происходит на экране, и видят некоторое превью контента перед его полной загрузкой и отображением.

В веб-приложении эта концепция может заключаться в отображении макетов текста, изображений или других элементов контента, называемых skeleton screens. Если вы до сих пор не поняли о чём речь, то посмотрите на то, как это используют на практике такие «малоизвестные компании» как Facebook, Google, Slack и другие:

Улучшаем юзабилити в вебе или создание  Skeleton Screens с CSS
Пример skeleton screens Slack
Улучшаем юзабилити в вебе или создание  Skeleton Screens с CSS
Пример skeleton screens Facebook

Пример

Допустим, вы создаете веб-приложение, в котором люди могут делиться впечатлениями о поездках и рекомендовать посещённые места, ваш основной контент в нём может выглядеть примерно так:

Улучшаем юзабилити в вебе или создание  Skeleton Screens с CSS

Вы можете минимизировать превью до основных визуальных форм, до так называемого скелета пользовательского интерфейса, как на примере:

Улучшаем юзабилити в вебе или создание  Skeleton Screens с CSS

Всякий раз когда ваше веб приложение обращается к серверу, пользователю показывается skeleton screens, до тех пор пока фоном подгружается контент. Как только содержимое страницы будет полностью загружено, просто замените скелет на актуальный контент. Это можно сделать с помощью ванильного JavaScript или с помощью такой библиотеки, как React.

В качестве скелета можно использовать изображение, но это повлечет потерю производительности на запрос и загрузку данных. Кроме того, изображения не адаптивны, и если вы когда-либо решите изменить стиль карты контента, то придется дублировать изменения в изображении скелета.

Лучшим решением для создания skeleton screens будет использование только лишь CSS. Это не требует никаких дополнительных запросов, минимальные объемы данных, даже никакой дополнительной разметки не потребуется. Кроме того, вы изначально можете создать скелет таким образом, что в будущем при необходимости сможете легко изменить дизайн.

Рисуем skeleton screen в CSS

Сначала нужно нарисовать основные фигуры, которые будут составлять макет скелета. Это можно сделать, добавив различные градиенты в свойство background-image. По умолчанию линейные градиенты отрисовываются сверху вниз с различными цветовыми переходами. Если мы просто определим один цвет и оставим остальные прозрачными, мы сможем рисовать фигуры.

Имейте в виду, что несколько свойств background-image накладываются друг на друга, поэтому порядок важен. Последнее определение градиента будет сзади, первое-спереди.

.skeleton {
  background-repeat: no-repeat;
  background-image: 
    /* layer 2: avatar */
    /* white circle with 16px radius */
    radial-gradient(circle 16px, white 99%, transparent 0),
    /* layer 1: title */
    /* white rectangle with 40px height */
    linear-gradient(white 40px, transparent 0),
    /* layer 0: card bg */
    /* gray rectangle that covers whole element */
    linear-gradient(gray 100%, transparent 0);
}

Эти фигуры растягиваются, чтобы заполнить все пространство, как обычные элементы уровня блока. Если мы хотим изменить это, мы должны определить четкие размеры для них. Пары значений в background-size задают ширину и высоту каждого слоя, сохраняя тот же порядок, который мы использовали в background-image:

.skeleton {
  background-size:
    32px 32px,  /* avatar */
    200px 40px, /* title */
    100% 100%;  /* card bg */
}

Последним шагом является расположение элементов на макете. Это работает так же, как position:absolute, со значениями, представляющими свойства left и top. Мы можем, например, имитировать заполнение 24px для аватара и заголовка, чтобы соответствовать внешнему виду макета реального контента.

.skeleton {
  background-position:
    24px 24px,  /* avatar */
    24px 200px, /* title */
    0 0;        /* card bg */
}

Разбавим скелет кастомными свойствами

Это хорошо работает в простом примере, но если мы хотим создать что-то более сложное, CSS быстро становится грязным и трудночитаемым. И если вы передадите такой код другому разработчику, то вы узнаете очень много нового о себе и своей семье до 7-го колена. Поддержка этого проекта превратится в легкий адок локального масштаба.

К счастью, для написания стиля скелета  более лаконично, мы можем использовать кастомные свойства CSS.

.skeleton {
  /*
    define as separate properties
  */
  --card-height: 340px;
  --card-padding:24px;
  --card-skeleton: linear-gradient(gray var(--card-height), transparent 0);

  --title-height: 32px;
  --title-width: 200px;
  --title-position: var(--card-padding) 180px;
  --title-skeleton: linear-gradient(white var(--title-height), transparent 0);

  --avatar-size: 32px;
  --avatar-position: var(--card-padding) var(--card-padding);
  --avatar-skeleton: radial-gradient(
    circle calc(var(--avatar-size) / 2), 
    white 99%, 
    transparent 0
  );

  /* 
    now we can break the background up 
    into individual shapes 
  */
  background-image: 
    var(--avatar-skeleton),
    var(--title-skeleton),
    var(--card-skeleton);

  background-size:
    var(--avatar-size),
    var(--title-width) var(--title-height),
    100% 100%;

  background-position:
    var(--avatar-position),
    var(--title-position),
    0 0;
}

Мало того, что так более читабельно, так ещё и будет легче изменить некоторые значения позже. Кроме того, мы можем использовать переменные (например, — avatar-size, —card-padding и т. д.) для определения стилей актуального макета и всегда синхронизировать с версией скелета .

Добавление @media для настройки мараметров ширины скелета теперь также довольно просто:

@media screen and (min-width: 47em) {
  :root {
    --card-padding: 32px;
    --card-height: 360px;
  }
}

Внимание: данные свойства и решения имеют очень хорошую поддержку браузерами, но к сожалению не 100%. Такие «жемчужины» как IE/Edge немного отстают в этом вопросе, но это решаемо используя переменные Sass.

Добавляем анимацию

Чтобы получить более модный/стильный/молодёжный вид, добавим в наш скелет анимацию, которая заменит нам банальный вид индикатора загрузки. Всё что нам потребуется, так это добавить новый градиент на верхний слой и анимировать его позицию с помощью @keyframes.

Вот полный пример, того что у нас сегодня получилось:

/*
 * Variables
 */

:root {  
  --card-padding: 24px;
  --card-height: 340px;
  --card-skeleton: linear-gradient(lightgrey var(--card-height), transparent 0);
  
  --avatar-size: 32px;
  --avatar-position: var(--card-padding) var(--card-padding);
  --avatar-skeleton: radial-gradient(circle 16px at center, white 99%, transparent 0
  );
  
  --title-height: 32px;
  --title-width: 200px;
  --title-position: var(--card-padding) 180px;
  --title-skeleton: linear-gradient(white var(--title-height), transparent 0);
  
  --desc-line-height: 16px;
  --desc-line-skeleton: linear-gradient(white var(--desc-line-height), transparent 0);
  --desc-line-1-width:230px;
  --desc-line-1-position: var(--card-padding) 242px;
  --desc-line-2-width:180px;
  --desc-line-2-position: var(--card-padding) 265px;
  
  --footer-height: 40px;
  --footer-position: 0 calc(var(--card-height) - var(--footer-height));
  --footer-skeleton: linear-gradient(white var(--footer-height), transparent 0);
  
  --blur-width: 200px;
  --blur-size: var(--blur-width) calc(var(--card-height) - var(--footer-height));
}

/*
 * Card Skeleton for Loading
 */

.card {
  width: 280px; //demo
  height: var(--card-height);
  
  &:empty::after {
    content:"";
    display:block;
    width: 100%;
    height: 100%;
    border-radius:6px;
    box-shadow: 0 10px 45px rgba(0,0,0, .1);

    background-image:
      linear-gradient(
        90deg, 
        rgba(lightgrey, 0) 0, 
        rgba(lightgrey, .8) 50%, 
        rgba(lightgrey, 0) 100%
      ),                          //animation blur
      var(--title-skeleton),      //title
      var(--desc-line-skeleton),  //desc1
      var(--desc-line-skeleton),  //desc2
      var(--avatar-skeleton),     //avatar
      var(--footer-skeleton),     //footer bar
      var(--card-skeleton)        //card
    ;

    background-size:
      var(--blur-size),
      var(--title-width) var(--title-height),
      var(--desc-line-1-width) var(--desc-line-height),
      var(--desc-line-2-width) var(--desc-line-height),
      var(--avatar-size) var(--avatar-size),
      100% var(--footer-height),
      100% 100%
    ;
    
    background-position:
      -150% 0,                      //animation
      var(--title-position),        //title
      var(--desc-line-1-position),  //desc1
      var(--desc-line-2-position),  //desc2
      var(--avatar-position),       //avatar
      var(--footer-position),       //footer bar
      0 0                           //card
    ;

    background-repeat: no-repeat;
    animation: loading 1.5s infinite;
  }
}

@keyframes loading {
  to {
    background-position:
      350% 0,        
      var(--title-position),  
      var(--desc-line-1-position),
      var(--desc-line-2-position),
      var(--avatar-position),
      var(--footer-position),
      0 0
    ;
  }
}


/* 
 * Demo Stuff
 */

body {
  min-height:100vh;
  background-color:#FFF;
  display:flex;
  justify-content:center;
  align-items:center;
}

И выглядит это всё примерно так:

Улучшаем юзабилити в вебе или создание  Skeleton Screens с CSS

Перевод статьи автора MAX BÖCK: Building Skeleton Screens with CSS Custom Properties 

Улучшаем юзабилити в вебе или создание  Skeleton Screens с CSS

Гик, хакинтошник, линуксоид, считаю что немного умею в вебдизайн и сайтостроение