Социальные кнопки своими руками

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

Недостатки популярных share-скриптов

Большинство популярных скриптов для добавления на сайт блока «поделиться», как правило, обладают одним или несколькими из следующих недостатков:

  1. Подключаемые для их работы js-файлы и впоследствии генерируемый ими контент скрываются блокировщиками рекламы, в которых установлена подписка Fanboy’s Social Blocking List, состоящая из большого числа косметических и сетевых фильтров. Например, плагины AdBlock Plus и uBlock Origin включают эту подписку сразу после добавления их в браузер, и пользователь по умолчанию не увидит «социальный» блок;
  2. Скрипты обладают относительно большим размером, генерируют дополнительные запросы к внешним ресурсам и не соответствуют элементарным требованиям веб-производительности. Например, блок «поделиться» от Яндекс после подключения на страницу основного скрипта автоматически загрузит в секцию <head> скрипт «Метрики», который в целях сбора статистики будет периодически отправлять соответствующие запросы во время пребывания посетителя на сайте;
  3. Социальные кнопки не адаптированы под небольшие экраны и имеют статичные размеры высоты и ширины, заданные в абсолютных единицах. Например, кнопки от «Share42» предоставляются на выбор лишь в нескольких пиксельных габаритах. При использовании на странице вертикального ритма потребуется дополнительный CSS для их выравнивания;
  4. Некоторые скрипты являются плагинами, зависимыми от JS-библиотек, и не будут без них работать;
  5. Генерируемый контент содержит дополнительные элементы, нежелательные с точки зрения SEO. Например, скрипт «AddThis» добавляет помимо кнопок утилитарный <iframe>, который скрывается следующим «некрасивым» inline-CSS: visibility: hidden; height: 1px; width: 1px; position: absolute; top: -9999px.

Создание блока «поделиться»

Собственный скрипт share должен избегать вышеперечисленных недостатков, а значит обладать небольшим весом, генерировать только необходимые семантически верные элементы и не зависеть от сторонних ресурсов. Добавляемый скриптом блок с социальными кнопками, в свою очередь, должен быть адаптивным и не содержать значений идентификаторов, классов и атрибутов, которые могут быть отфильтрованы блокировщиком рекламы.

HTML-разметка

В результате «сборки» блока с социальными кнопками получается HTML-код, где значение атрибута data-type для элементов <button> является условным названием конкретного социального сервиса — VK (vk), OK.ru (ok), Twitter (tt), Facebook (fb), Google Plus (gp), LiveJournal (lj), Evernote (en) и Pocket (pt), а data-btn для блока — перечень необходимых подключаемых сервисов через запятую в виде их условных названий:

<div id="shar3-box" data-btn="vk,ok,tt,fb,gp,lj,en,pt">
  <button type="button" data-type="vk" aria-label="VK" class="shar3-box-btn"></button>
  <button type="button" data-type="ok" aria-label="Odnoklassniki" class="shar3-box-btn"></button>
  <button type="button" data-type="tt" aria-label="Twitter" class="shar3-box-btn"></button>
  <!-- etc. -->
</div>

По мнению автора, с точки зрения SEO использование пустого элемента <button> вместе с aria-label предпочтительнее, нежели добавление ссылки <a> со скрытым текстом или без анкора.

CSS-оформление

Чтобы не загружать на сайт лишние спрайты и обеспечить масштабирование кнопок, изображения для них будут выводиться через CSS в виде закодированного в base64 SVG (для удобочитаемости в приведенном примере данные SVG отсутствуют). Соответствующие иконки взяты с сайта Simple Icons и находятся в свободном доступе. Для высоты, ширины и отступа от кнопок используются относительные единицы измерения — em. Таким образом, масштаб блока и его дочерних элементов будет зависеть от родительского размера шрифта.

Процесс стилизации кнопок можно облегчить благодаря SCSS:

$btn-scale-factor: 2; // 2em - высота и ширина кнопок
$btn-margin-right: .25; // .25em  - отступ справа

// [имя кнопки] [цвет фона кнопки] ['svg-иконка кнопки в base64']
$btn: (
  vk #6996C7 'base_64_code',
  ok #EE8208 'base_64_code',
  tt #28C3FF 'base_64_code',
  fb #496194 'base_64_code',
  gp #FF6D4A 'base_64_code',
  lj #21A5D8 'base_64_code',
  en #7AC142 'base_64_code',
  pt #EF485F 'base_64_code'
);

#shar3-box {
  min-height: #{$btn-size}em; // предотвращение визуального "дерганья" блока во время генерации кнопок
  user-select: none;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  -o-user-select: none;

  .shar3-box-btn {
    transition: opacity .1s ease-in-out;
    -webkit-transition: opacity .1s ease-in-out;
    border: 0;
    opacity: .75;
    margin: 0 #{$btn-margin-right}em 0 0;
    padding: 0;
    display: inline-block;
    font: inherit;
    background-size: percentage($btn-icon-scale);
    background-position: 50% 50%;
    background-repeat: no-repeat;
    width: #{$btn-size}em;
    height: #{$btn-size}em;
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    cursor: pointer;

    &:focus { outline: 0; }
    &:hover { opacity: 1; }
    &:last-child { margin-right: 0; }

    @each $name, $bg-color, $icon in $btn {
      &[data-type="#{$name}"] {
        background-image: url(data:image/svg+xml;base64,#{unquote($icon)});
        background-color: $bg-color;
      }
    }
  }
}

JavaScript

JS решает две главные задачи — генерацию кнопок в блок с идентификатором #shar3-box и открытие социального сервиса по клику на соответствующую ему кнопку. Обработчик события click добавляется на сам блок, после чего проверяется, кликнул ли пользователь на дочерний элемент <button>, и, если так, посредством атрибута data-type открывается необходимый социальный сервис. Таким образом, используется один обработчик для всего блока, а не несколько для каждой из кнопок.

Для правильной работы скрипта понадобятся вспомогательные функции, обеспечивающие открытие нового окна и парсинг основных необходимых meta-данных о странице: url, заголовок <title> и описание <meta name="description" />.

/**
 * Основная функция
 */
var shar3 = function() {

  // нахождение блока и его атрибута data-share
  var shareBox = document.getElementById("shar3-box"),
    shareBoxBtns = shareBox.getAttribute("data-btn").split(",");

  // добавление aria-label
  var getLabel = function(a) {
    var label;
    switch (a) {
      case "vk":
        label = "VKontakte";
        break;
      case "tt":
        label = "Twitter";
        break;
      case "ok":
        label = "Odnoklassniki";
        break;
      case "fb":
        label = "Facebook";
        break;
      case "gp":
        label = "Google+";
        break;
      case "lj":
        label = "LiveJournal";
        break;
      case "en":
        label = "Evernote";
        break;
      case "pt":
        label = "Pocket";
        break;
      default:
        label = "Поделиться";
        break;
    }
    return label;
  };

  // генерация кнопок
  for (var i in shareBoxBtns) {
    var btn = document.createElement("button");
    btn.type = "button";
    btn.classList.add("shar3-box-btn");
    btn.setAttribute("data-type", shareBoxBtns[i]);
    btn.setAttribute("aria-label", getLabel(shareBoxBtns[i]));
    shareBox.appendChild(btn);
  }

  /**
   * Функция для открытия нового окна
   * @private
   * @param {Object} set - объект с настойками (имя, высота и ширина окна)
   */
  var win = function(set) {
    if (!set.url) return;
    set = Object.assign({}, {
      w: 650,
      h: 570,
      name: ""
    }, set);
    var dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left,
      dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top,
      width = window.innerWidth ? window.innerWidth : d.documentElement.clientWidth ? d.documentElement.clientWidth : screen.width,
      height = window.innerHeight ? window.innerHeight : d.documentElement.clientHeight ? d.documentElement.clientHeight : screen.height,
      left = ((width / 2) - (set.w / 2)) + dualScreenLeft,
      top = ((height / 2) - (set.h / 2)) + dualScreenTop;
    var open = window.open(set.url, set.name, 'scrollbars=yes, width=' + set.w + ', height=' + set.h + ', top=' + top + ', left=' + left);
    if (window.focus) open.focus();
  };

  /**
   * Функция открытия сервиса share
   * @param {Event} e - событие, получаемое при "click"
   */
  var shareIt = function(e) {
    var u = window.location.href; // url
    var t = document.title; // title
    var d = document.head.querySelector("meta[name=description]") || ""; // meta description
    switch (e.target.getAttribute("data-type")) {
      case "vk":
        win({
          url: "//vk.com/share.php?url=" + u + "&title=" + t + "&description=" + d,
          name: "vk"
        });
        break;
      case "ok":
        win({
          url: "//connect.ok.ru/offer?url=" + u + "&title=" + t + "&description=" + d,
          name: "ok"
        });
        break;
      case "tt":
        win({
          url: "//twitter.com/intent/tweet?text=" + t + "&url=" + u,
          name: "tt"
        });
        break;
      case "fb":
        win({
          url: "//facebook.com/sharer.php?m2w&" + 'u=' + u,
          name: "fb"
        });
        break;
      case "gp":
        win({
          url: "//plus.google.com/share?url=" + u,
          name: "gp"
        });
        break;
      case "lj":
        win({
          url: "//livejournal.com/update.bml?event=" + u + "&subject=" + t,
          name: "lj"
        });
        break;
      case "en":
        win({
          url: "//www.evernote.com/clip.action?url=" + u + "&title=" + t,
          name: "en",
          w: 1024,
          h: 720
        });
        break;
      case "pt":
        win({
          url: "//getpocket.com/edit?url=" + u + "&title=" + t,
          name: "pt"
        });
        break;
      default:
        return false;
    }
  };

  // обработчик клика для блока share
  shareBox.addEventListener("click", function(e) {
    if (e.target !== e.currentTarget) shareIt(e);
    e.preventDefault();
  });
};

Пример работы блока share

Скрипт share отлично работает во всех современных браузерах. В Internet Explorer 11 для метода Object.assign() необходимо добавить полифилл.

Для вкрапления блока «поделиться» на страницу достаточно добавить соответствующий CSS, поместить скрипт перед закрывающим тегом </body> и вызывать функцию shar3(). Необходимые для вывода кнопки настраиваются непосредственно в блоке:

<div id="shar3-box" data-btn="vk,ok,tt,fb,gp,lj,en,pt"></div>

В заключение

Созданный блок share представлен в наиболее простом варианте с самыми основными функциями, поэтому он может быть cущественно доработан, например, в части прикрепления изображений вместе с отправляемым контентом, в парсинге метатегов Open Graph, в количестве выводимых кнопок социальных сервисов, в возможностях расположения блока и т. п.