strokoff

Разрабатываем переиспользуемый web-компонент для лайков

В рамках цикла статей о веб-компонентах мы рассмотрим пример реализации компонента лайков wc-likes, пройдемся по шагам реализации и интегрируем лайки прямо на webislife 😄 поехали!)

wc-like будет автономным пользовательским элементом – «полностью новый» элемент, расширяющий абстрактный класс HTMLElement

Функциональная часть

Что такое компонент лайка? это некий HTMLElement который обычно нам показывает число лайков и иконку с сердечком. Если бы мы его верстали, получалась бы +- такая emmet конструкция wc-likes>button+span Начнем с описания типов и конструктора нашего компонента

public Liked:boolean = false
public LikesRendered:boolean = false
public LikesCount:number = 0
public LikesFetch:boolean = false
public LikesCountNode:Node
public LikesIcon:HTMLElement
public LikesPhrases:WCLikesLang
public LikesPhrasesLang:WCLikesPhrasesKey|string

constructor() {
  super();
  const defaultLang = 'ru';

  this.LikesCount = Number(this.getAttribute('value'));
  this.LikesPhrasesLang = defaultLang;
  this.LikesPhrases = WCLikesLangs[this.LikesPhrasesLang];
  this.classList.add('wc-likes');
}

Вроде все понятно, добавляем себе возможность для поддержки мультиязычности и навешиваем класс wc-likes на элемент чтобы каждый раз его не писать в HTML

/**
* Создаем основные node элементы для нашего компонента лайков
*/
buildElement() {
  //Кнопка с иконкой
  this.LikesIcon = document.createElement('button');
  //Like SVG icon - иконку можно поменять на свою, я вставил прямо в код
  this.LikesIcon.innerHTML = '';
  //Добавим слушателя события нажатия на кнопку
  this.LikesIcon.onpointerup = ev => this.onLikesSubmit(ev);
  //Создадим textNode с количеством лайков
  this.LikesCountNode = document.createTextNode(`${this.LikesCount}`);
  //Добавим элементы в DOM нашего компонента
  this.append(this.LikesIcon);
  this.append(this.LikesCountNode);
}

Рассмотрим метод обработки отправки нашего лайка

/**
  * Логика обработки отправки лайка
  * @param ev
  */
  async onLikesSubmit(ev:Event) {{
    //Если уже лайкнули
    if(this.Liked) {
      this.setAttribute('data-hint', this.LikesPhrases.alreadyLike);
      return;
     }
     this.setAttribute('fetch', '1');
     const fetchLikesStatus = await this.fetchSubmitLikes().catch(err => {
      this.setAttribute('data-hint', err.toString());
     });
     this.removeAttribute('fetch');
     switch (fetchLikesStatus) {
      case 200:
        const newCount = this.LikesCount + 1;
        this.setAttribute('value', String(newCount));
        this.setAttribute('liked', String(1));
        break;

      default:
       this.setAttribute('data-hint', this.LikesPhrases.likeError);
      break;
     }
  }};

Ничего сложного, мы проверяем лайкал ли уже пользователь и если нет, отправляем запрос в API и навешиваем -fetch класс CSS

Но на самом деле нам осталось реализовать еще 2 метода отправки и получения количества лайков, эта реализация зависит от сайта и его системы и не входит в философию веб-компонента, я расскажу про интеграцию лайков к постам на webislife.ru в следующей статье)

  /**
  * ABSTRACT METHOD NEEDS YOUR IMPLEMENTATION SEE wc-likes-post.ts example
  * submitLike to backend
  */
  async fetchSubmitLikes():Promise {
  const PromiseAPIStatus:number = await new Promise((resolve, reject) => {
    setTimeout(() => resolve(200), 1000);
  });
  return PromiseAPIStatus;
}

/**
* ABSTRACT METHOD NEEDS YOUR IMPLEMENTATION SEE wc-likes-post.ts example
* fetch likes count from backend
*/
async fetchAsyncLikes(params:any):Promise {
const PromiseAPIStatus:number = await new Promise((resolve, reject) => {
setTimeout(() => resolve(200), 1000);
});
return PromiseAPIStatus;
}

Два метода, которые осталось реализовать)

А ну и конечно же добавим немного SCSS стилей и CSS анимаций) куда же без этого

SCSS стили для компонента
@keyframes wcLikesSubmit {
    0% {
        visibility: visible;
        top: 0;
        opacity: 1;
    }
    100% {
        opacity: 0;
        top: -100px;
        font-size: 4em;
    }
}
@keyframes wcLikesFetch {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}
.wc-likes {
    display: inline-flex;
    align-items: center;
    padding: 0px 1em 0px 0px;
    border:1px solid var(--color-blue-light-100);
    border-radius: 20px;
    transition: all 0.2s ease;
    user-select:none;
    &:hover {
        background-color: var(--color-blue-light-50);
        border-color: var(--color-blue-light-50);
    }
    &:after {
        min-width: 100px;
        text-align: center;
        white-space: nowrap;
        background-color: var(--color-blue-light-50);
        color: var(--color-blue-500);
    }
    & > button {
        user-select: contain;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        position: relative;
        width: 2em;
        height: 2em;
        transition: all 0.2s ease;
        margin-right: 10px;
        color: var(--color-red-500);
        background-color: #fff;
        border: none;
        border-radius: 50%;
        outline: none;
        cursor: pointer;
        &:hover {
            background-color: var(--color-red-100);
            border: none;
            & > svg {
                fill: var(--color-red-500);
            }
        }
        &:after {
            visibility: hidden;
            color: var(--color-red-500);
            content: '♥';
            position: absolute;
        }
        & > svg {
            fill: var(--color-red-100);
            min-width: 16px;
        }
    }
    //fetch like state
    &.-fetch {
        opacity: 0.8;
        pointer-events: none;
        & > button:before {
            position:absolute;
            border: 2px solid var(--color-red-100);
            border-top: 2px solid var(--color-red-500);
            border-radius: 50%;
            display: block;
            width: 2em;
            height: 2em;
            content: '';
            animation: wcLikesFetch 1s linear infinite;
        }
    }
    //liked state
    &.-liked {
        background-color: var(--color-blue-light-100);
        border-color: var(--color-blue-light-100);
        & > button {
            background-color: var(--color-red-500);
            color: #fff;
            &:after {
                animation-fill-mode: forwards;
                animation-duration: 0.4s;
                animation-name: wcLikesSubmit;
            }
            & > svg {
                fill: #fff;
                stroke: none;
            }
        }
    }
}

Веб компоненты — это замена современных фреймворков?

Однозначно нет — есть конечно представители фронтенда с перегибами в сторону нативных компонентов, как и есть армии фанатов react/vue/angular но у всех этих фреймворков уже есть поддержка web компонентов

  1. Vue и web компоненты 
  2. web компоненты вместе React
  3.  angular guide elements руководство на 🇺🇸

Для тех кому хочется побыстрее попробовать, уже готовый репозиторий на github и готовый npm пакет npm-wc-likes также пакет доступен на github.


Последняя редакция 31 января, 2023 в 03:01