Учим JavaScript на примере: Делаем собственный виджет, скин, стиль, плагин для голоса. Очень подробно разжевываю код :)
В посте мы рассмотрим весь JavaScript код который задействован в этом скине для голоса.
Скрипт сейчас находится здесь https://greasyfork.org/ru/scripts/26833-golos-vik
Его можно установить с помощью tampermonkey и других плагинов для браузера. Обновлять можно одним кликом. НО! Такие скрипты могут быть опасны, потому даже перед обновлением моего скрипта - проверяйте код или пишите мне с вопросом - действительно-ли я обновлял код. Или кто-то взломал мою страничку на greasyfork.org ))
Собственный скрипт вы сможете опубликовать в репозиториях и поделится им со всеми или пользоваться им единолично, сделав конкретно под себя.
После прочтения этой простыни - сделать собственный скин, плагин, виджет для голоса или любого другого сайта для вас будет очень легко :)
При желании и в теории, вы можете даже на этом заработать :) Например делая такие скины для популярных соцсетей.
Например сделать суровый скин для пользователей одноклассников , которые сейчас в тюрьме :)
Или стилизовать порнхаб под ютуб для тех, кто любит ручной труд в обеденный перерыв или вообще делать на заказ сайты похожими на exel или 1c бухгалтерию, что бы офисные работники могли сидеть в соцсетях без оглядки на начальство :)
Очень подробно разбираем код
Как выглядит плод работы скина можно увидеть в ролике youtu.be/X8VB9a6suDg
Так как голос пока не поддерживает синтаксис кода (правда я не пробовал встраивать сюда iframe от codepen или js fiddle ), то придется все отображать скринами с notepad++
Если мы будем делать userscript для greasyfork (он мне показался безопасней других), то в вам понадобиться указать вначале некоторые параметры:
- name будет определять название скрипта в каталоге
- version необходимо менять каждый раз при обновлении
- include важный параметр, который определяет для какой страницы скрипт. В данном случае указан домен golos.io/* где оператор * означает, что скрипт будет на всех связанных страничках с golos.io
На заметку (далее nota bene - NB) Если вы заметите скрипт, в котором нет такого однозначного указания домена, значит автор скрипта хочет выполнять его на любом сайте, который вы посещаете. Это может быть опасно для вас или по крайней мере неудобно. Например если кто-то решит заменять все ваши ссылки на страницах на рекламные. Или просто встраивать баннер с рекламой на каждом сайте, который вы посетили.
require позволит вам указать необходимые библиотеки из доверенных источников. Поддерживается cdnjs и jsdelivr, а это значит, что у вас выбор из сотни тысяч отличных плагинов. В нашем случае мы подключили jquery. Все его знают и скрипт будет написан на нем.
Блок /* текст */ не обязателен, я просто оставил в нем произвольную заметку.
Javascript
Добавляем свой css стиль, виджеты, кнопки и меняем разрешение на фотографиях в ленте постов
Разберем это скрин
В 19 строке у нас переменная (var от англ. variable) inlinecss
в которую мы вставим наши стили. На скрине для компактности стили пустые, на деле же выглядит примерно так:
var inlinecss = "<style>body{background:black}#content{color:white}</style>";
Стили должны быть одной строкой и неплохо бы их прогонять через минификатор.
На 20 строчке проделываем почти тоже самое, но в переменную вставляем уже не стили, а html. Например в моем скине там боковая панель с ответами, копка прокрутки страницы вверх и другие мелкие элементы.
На 22 и 23 строке мы вставляем содержание переменных в страницу. Стили в блок head, а html в конец страницы, так как далее мы его будем позиционировать css свойством position:fixed
С 27 по 41 строку мы делаем фотографии большими. Дело в том, что в голосе, все фото хостятся на внешних сайтах, что бы избежать многих проблем кроссдоменности, конфликтов https, а так же получить контроль над этими фотками голос использует проксирование.
Например если вы добавите в текст ссылку вида https://vk.com/album/photo.jpg
то на деле она будет отображена так:
- Для превью https://imgp.golos.io/256x128/
https://vk.com/album/photo.jpg
- Одноколоночный макет для узких экранов https://imgp.golos.io/640x480/
https://vk.com/album/photo.jpg
- Аватар https://imgp.golos.io/36x36/
https://vk.com/album/photo.jpg
В каждом из случаев одно и тоже фото проходит оптимизацию по разному.
В своем скине я сделал одноколоночный макет на подобие tumblr или fb , который подразумевает большое фото или фотосет. Но проблема оказалась в том, что источник фото на главной это imgp.golos.io/256x128/ - такое разрешение для больших плиток постов слишком низкое и нам нужно как-то получить доступ к нормальному размеру картинок. Вот тут мы и обращаемся к imgp.golos.io/640x480/ ведь мы знаем, что там такие же фотки, только с нормальным размером. Таким образом все, что нам нужно, это заменить на главной в ссылках 256 x 128
на 640 x 480
. Причем сделать это не только для существующих постов, но и тех, которые подгрузятся далее. Делаем следующее
На 27 и 29 строчке мы делаем переменные со значениями, которое будем менять местами.
С 30 по 35 строчку у нас функция, которая перебирает регулярным выражением все значения путей к картинкам <img src=
во всех фото с классом .PostSummary_image
На 36 строчке мы запускаем функцию. Это происходит только один раз. И проблема будет в том, что когда подгрузим новые посты, функция их не обработает, так как раньше их не было на странице.
Решаем проблему тем, что с 39 по 41 строчку мы заставляем срабатывать функцию каждый раз, когда происходит динамическое добавление любого контента в страницу, это событие выглядит как DOMNodeInserted
. Теперь у нас обновляются фото не только в существующих постах, но и в тех, которые будут добавлены бесконечным скроллингом.
Создаем нашу самую большую функцию waitfor, которая будет выполняться рекурсивно
Получаем небходимые параметры для виджета "ответы" и возможности управлять стилями в аккаунте голоса. А так же добавить неограниченное число настроек в будущем!
Пожалуй самый интересный костыль кусок кода
Как все работает на практике.
В видеоролике были примеры того, как работают настройки в аккаунте, ниже я опишу как пользователь сможет пользоваться параметрами на голосе.
Идея в том, что бы юзер мог прописать свои настройки где-то у себя в аккаунте , но настроек в аккаунте всего несколько.
Язык и валюта не подойдет, а вот строка со ссылкой на аватарку... Хм, да в ней можно устроить Лас Вегас :)
Дело в том, что если мы добавим в конце любого url (ссылки) знак вопроса, то это не повлияет на адрес сайта или фотографии, однако ссылка будет содержать параметры. Это часто используют партнерки для определения id партнера.
Но мы используем это для хранения произвольных параметров!
Магия происходит на строках 56-66, где мы считываем параметры, которые указаны после значка ?
в нашей ссылке на юзерпик - аватар.
Например мы добавляем в конце
../ava.jpg?css=[#content{background:black;color:white;}]&topics=OFF
Далее на все тех же 56-66 строчках мы получим и отфильтруем все, что после ?
Отфильтруем таким образом, что из полученной строки мы сделаем набор опций похожих на это:
- параметр1=значение
- параметр2=значение
- параметр3=значение
- параметр4=значение
Далее в скрипте мы сможем применять их как угодно, например как переключатель, как в случае с топиками topics=OFF
- таким же методом можно будет отключить любой элемент на сайте. Значение может быть не только OFF и служить не только как переключатель. В случае с ?css=[#content{background:black;color:white;}]
мы сделаем следующее:
Получим все что после css= и перед ближайшим &
. Затем в скрипте мы укажем, что весь этот стиль должен быть применен, тогда пользователь редактируя свою ссылку аватара сможет сделать голос другого цвета, размера, вида. Например, если вам на работе не разрешают зависать в соцсетях - вы сможете стилизовать голос под вид своей рабочей программы и не палиться перед начальством, продолжая постить мемасики и котиков)
Итак, мы получили и отфильтровали параметры после ссылки, но задействуем и обработаем мы их позднее, так как у нас есть проблема:
Пришлось немного помучатся, так как проблема взаимодействия скина с голосом была в том, что голос на react и большинство контента появляется не сразу, а динамически. Мне нужно было как-то получить ссылку на страницу ответов текущего пользователя и ссылку на аватарку текущего пользователя.
С 45 поооооо 150 строку у нас будет функция waitfor
, которая будет содержать в себе много других функций описанных ниже. Вызвана она будет только потом на 151 строке, а затем будет рекурсивно вызываться внутри самой себя до тех пор, пока ваш аватар не появится. Учтите, что весь код ниже выполняется внутри нее:
47 и 48 строчка это переменные с двумя значениями - первая переменная хранит в себе html класс аватарки, которая еще не появилась. Вторая переменная хранит в себе ссылку на аватар.
Переменная на строке 51 хранит в себе ссылку на профиль пользователя, которую мы возьмем с ссылки в которую обвернут аватар. Это ссылка есть родительский элемент аватара и потому используем .parent().attr('href');
что бы получить атрибут href у ссылки.
Но на старте загрузки эти переменные ничего не хранят, так как эти элементы еще не подгрузились. Решаем мы эту проблему позже, а пока создадим еще одну интересную функцию на строках 56-66, которая поможет нам сделать возможность пользователю самому настраивать свой голос.
С помощью регулярного выражения и перебора строки мы фильтруем все лишнее в ссылке на аватар и получаем только значения после знака ?
Но мы не вызываем пока эту функцию, пока еще рано, так как необходимые параметры еще не загрузились в наши переменные. Мы еще вернемся к этой функции, а пока создадим еще одну:
С 68 по 70 строку у нас функция ajax загрузки ваших ответов
NB: Большинство кода связанно именно с этим, нам нужно было получить значение имени текущего пользователя. Если бы каждый указывал в скрипте свое имя- кода было бы намного меньше и не было бы таких ужасных костылей, но так не интересно :)
Подробно о виджете ответов
Ответы загружает строка:
$('.answertarget').load('/usernameUrl + '/recent-replies/ #posts_list');
Работает это так: в html элемент div который мы создали в самом начале с классом answertarget мы загружаем (.load) содержимое страницы, ссылка на которую выглядит как:
Добавляем к ссылке имя из переменной usernameUrl
и прибавляем значком + путь к ответам
@ ваше_имя/recent-replies/
так как мы будем вставлять в виджет не всю страницу, а только поле ответов, которое обернуто в id #posts_list
, то в конце ссылке укажем этот id.
Вот так вот одной строчкой мы добиваемся появления ответов там, где нам хочется
NB: Вы можете указать любую ссылку в рамках домена golos.io вместо
/usernameUrl + '/recent-replies/ #posts_list
И тогда в виджет будет загружать контент с той страницы, которую вы укажете. Например для ваших комментариев укажите
/usernameUrl + '/posts/ #posts_list
Или замените переменную /usernameUrl +
на ник любого пользователя и читайте его ответы, а не свои. Например, что бы читать мои:
$('.answertarget').load('/@vik/recent-replies/ #posts_list');
Разбираем скрин дальше.
Определяем, когда страница готова к нашим действиям
На 72 строке начинается наше главное условие if (Upic.length){
Оно звучит примерно как - если содержимое переменной Upic имеет длину
То есть мы проверям, не пуста ли переменная. И если она не будет пуста и значение Upic уже заполнено появившимся элементом голоса, а именно блоком вашего аватара с классом Userpic, то мы выполняем то, что внутри этого условия.
То есть это именно та часть, где мы будем вызывать большинство наших описанных ранее функций. Ведь они зависят от того, что ссылка на юзерпик должна была быть уже готова.
На 76 и 77 строке отладочные функции, которые я забыл удалить :) Они нужны были для того, что бы проверять в консоли, все ли правильно работает показывая содержимое переменных.
С 80 по 82 строку у нас функция для кнопочки обновления ответов Обновить ответы
Работает она так: ранее мы уже создали нашу переменную somehtml содержимое которой добавили в страницу. В этой переменной были все блоки, в том числе блок:
<div id="answerupdate"></div>
Поэтому мы можем сделать так
$('#answerupdate').on('click', function(){
Нажав на наш div мы запускаем функцию answerupdate(); Что именно делает функция, мы описали на строках 68-70
Идем дальше.
На строках с 85 по 95 мы описываем функцию кнопки Ответы.
- 85 строка - указываем базовую переменную, которая будет давать нам понять - нажимали кнопку или нет. В принципе это не обязательно для такой кнопки и можно было использовать toggle, но я хочу сделать более интуитивное поведение кнопки в следующей версии и мне нужно будет точно знать открыта панель или закрыта.
- 89 строчка - если нажали кнопку - вниз выезжает слайд с ответами
- 91 строчка - нажали и ответы свернулись.
- 94 строчка - вызываем описанную ранее функцию, которая меняет разрешение фотографий на строках 27-41. Это не обязательно, так как мы вызываем функцию каждый раз, когда в нашу страницу что-то вставляется. То есть сама загрузка ответов уже вызвала это функцию на строках 39-41.
Настройки скина в аккаунте на голосе
Не забываем, что мы все еще в условии начатом еще на строке 72, данное условие гласит, что выполнять то, что внутри него можно, только если аватар уже загружен.
- 103 строка мы помещаем в переменную
cleaning
содержимое которое создала для нас функция со строк 56-66 :getParameterByName
А конкретноgetParameterByName('css');
- 104 строка мы убираем из уже заполненной переменной cleaning первый и последний символ, зачем - напишу позже. Помещаем этого кастрата в переменную
stringcss
- 107 строка создаем переменную
cleanCSS
и в нее вставляем содержимое stringcss, предварительно обернув все содержимое в тег<style>
, что бы внутри переменной было все так:<style>содерижимое stringcss </style>
- 112 строка мы вставляем стили в head нашей страницы голоса.
- 116 - 118 строки оставлены на будущие параметры настроек
- 123 строка параметр, который будет отвечать за топики-метки. По желанию пользователь сможет отключить их. Вернее скрыть, а вызывать по кнопке как ответы.
Напомню, мы все еще в рамках условия с 72-й строчки "если Upic загружен, то выполнять код"
Отключаем топики
125 - 140 строки - условие: если параметр notopics имеет значение OFF, то...
126 строка - смещаем слайдом топики вверх за пределы поля зрения
128 строка - создаем кнопку с текстом "топики" и id slidetopics - дизайн кнопки описан в переменной inlinecss на 19 строке
130 - 138 строки - описываем поведение кнопки топики - делаем слайд топиков вверх или вниз по нажатию на кнопки
144 строка - О, наконец-то! Закрываем скобку условия "если Upic загружен, то выполнять код"
147 - 149 - условие else в противовес if. Если наш if, который есть условием "если аватар не загрузился" сообщает нам что - НЕТ, не загрузился, то в ход вступает else и говорит нам - ну если не загрузился, тогда делай это: setTimeout(waitfor, 1000);
А именно - вызывай каждую секунду (1000 миллисекунд) функцию waitfor. А это та самая функция внутри которой мы выполняли большинство других последних функций. Но мы не можем вот так взять и вызвать ее изнутри собственных кишок, потому на 150 строке мы ее закрываем и первый раз вызываем ее на 151 строке, будучи однажды вызванной, она-то и будет как дебильный эпилептик каждую секунду проверять наличие вашего аватара. Но не пугайтесь, такая проверка не нагружает браузер, вы не заметите тормозов. К тому же, она перестает ежесекундно выполняться когда ваш аватар появляется на странице.
Кнопка Вверх как необходимый атрибут
Маленкий кусочек кода, который позволяет работать кнопке вверх. Саму кнопку мы создали в переменной somehtml
и описали ее дизайн в переменной inlinecss
157 строчка - если вы прокрутили 200 пикселей, то на 158 строчке кнопка появится со скоростью 200мс
159 - 161 - если до верха менее 200px кнопка пропадет со скоростью 200мс
165 - 169 функция, которая при клике на кнопку скроллит вас в самое начало, что бы растоянние от вверха страницы было 0, скроллит со скоростью 300 мс
Заключение
На этом все, мы полностью разобрали весь userscript для голоса. В сотый раз напоминаю - не используйте такие скрипты без понимания того, как они работают, вы можете навредить себе!
PS Если у вас есть идеи, какие пользовательские настройки и параметры-переключатели можно ввести в скин голоса - пишите в комментарии.