Несколько лет назад была настоящая криптолихорадка: люди покупали и продавали биткоины, майнили эфир, скупали видеокарты и царил дух авантюризма. В те времена все искали проверенные источники, инсайды и аналитику, которые помогут принять верное решение о покупке.
Как и всегда, прогнозы — дело неблагодарное, и прогноз из серии «Пальцем в небо» будет всяко точнее. Поэтому мы решили автоматизировать этот процесс: сделать универсальную аналитическую машину, которая способна прогнозировать что угодно, от курса доллара до маршрута лосося на нерест. Метод прогнозирования хорошо изучен — наугад.
Сейчас наша машина умеет предсказывать поведение курса криптовалюты в ближайшей перспективе:
Что происходит на самом деле
Вся эта история про аналитическую машину — полный фейк. Мы придумали этот проект от начала и до конца, и логика была такая:
- Показать красивый прогресс-бар.
- Запустить его по кнопке, чтобы он начал работать.
- Пока он движется, выводим всякие сообщения, чтобы создать видимость работы. Очень важно, чтобы человек на том конце думал, что машина реально что-то прогнозирует.
- В конце показываем какое-то сообщение, мол, вот ваш прогноз. Сообщение может быть совершенно рандомным, на качество прогноза это не влияет.
В итоге получилась машина, которая просто показывает вам красивый прогресс-бар и выдаёт в итоге рандомную фразу. В принципе, ничем не хуже большинства прогнозных систем, только мы честно об этом говорим.
Зачем этот проект
Этот проект — упражнение в главной составляющей программистского дзена: бери чужое, вкручивай в своё. Мы будем пользоваться готовыми чужими решениями и получим в итоге работающий и довольно полезный продукт.
Прогресс-бар
Сделать красивый прогресс-бар — дело не пяти минут, поэтому мы поискали уже готовые решения в Сети и нашли вот такое:
Там было почти всё, что нам нужно, кроме двух вещей:
- прогресс-бар заполнялся слишком быстро,
- не менялись надписи в процессе работы.
Мы не будем показывать весь исходный код, потому что можно зайти в начальный проект и посмотреть — вместо этого покажем, что поменяли.
Замедляем прогресс-бар. Смотрим в function preload(imgArray)
:
Было:
$(imgArray).each(function() {
$('<img>').attr("src", this).load(function() {
$progress.animate({
width: "+=" + increment + "%"
}, 100);
});
Стало:
var i = 0;
while (i < 300) {
$progress.animate({
width: "+=" + 0.25 + "%"
}, 100);
i = i+1;
}
Вместо того, чтобы увеличивать прогресс на какую-то непонятную величину, мы прибавляем к нему на каждом шаге всего по 0.25 пункта. Это мало, поэтому двигаться будет медленно. Число 300 в цикле мы подобрали опытным путём: почему-то если прибавлять по 0.25, то именно за 300 шагов мы наберём 100%.
Выводим сообщения по ходу работы. Смотрим в ту же функцию.
Было: ничего, потому что в исходном проекте этого не требовалось.
Стало:
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 1) {
$('span').text(s1_final);
}
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 20) {
$('span').text(s20_final);
}
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 40) {
$('span').text(s40_final);
}
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 60) {
$('span').text(s60_final);
}
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 80) {
$('span').text(s80_final);
}
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 100) {
yes = false;
$('span').text('Примерная точность прогноза: ' + percent_final + '%');
$('p').text(result_final);
}
Первые 5 раз мы сравниваем показатели процентов на шкале с каким-то числом и если совпадаем — выводим то, что лежит в переменной. В последнем сравнении выдаём проценты и финальный текст. Про переменные расскажем ниже.
Готовим текст
У нас уже был проект, где мы генерировали текст случайным образом. Возьмём оттуда немного кода и перепишем для себя:
// этапы в прогресс-баре
var s1 = ['Собираю данные из интернета', 'Читаю Википедию', 'Смотрю паблики во ВКонтакте'];
var s20 = ['Обучаю нейросеть', 'Анализирую увиденное', 'Выделяю важное'];
var s40 = ['Готовлю технический анализ', 'Думаю о будущем', 'Осознаю важность момента'];
var s60 = ['Постулирую неведомое', 'Инкапсулирую полиморфизм', 'Кристаллизую суть'];
var s80 = ['Тыкаю пальцем в небо', 'Формализую данность как класс', 'Удаляю противоречия и вношу ясность'];
var result = ['Сначала крипта подрастет, потом обвалится. Потом снова подрастет, потом снова обвалится.', 'Можно закупать, хотя лучше сначала продать, но если важнее купить, то можно и не продавать', 'По всем показателям рецессия будет следовать за отскоком, но это вряд ли отразится на текущей ситуации, поэтому необходимо обратить внимание на волатильность.'];
// генератор случайных чисел в диапазоне от минимального до максимального
function randz(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// готовим итоговые значения
var s1_final = s1[randz(0, 2)];
var s20_final = s20[randz(0, 2)];
var s40_final = s40[randz(0, 2)];
var s60_final = s60[randz(0, 2)];
var s80_final = s80[randz(0, 2)];
var percent_final = randz(60, 99);
var result_final = result[randz(0, 2)];
Логика простая:
- готовим шаблоны текста для каждого этапа;
- вставляем функцию, которая вернёт нам случайное число в нужном диапазоне;
- заполняем финальные значения случайным образом.
Вернитесь немного назад и посмотрите: именно эти переменные мы использовали в разделе, когда сравнивали значения с процентами на шкале и выводили результат.
Собираем страницу
Основное мы сделали, осталось всё это красиво разместить в браузере. Берём код страницы исходного проекта, немного правим и получаем такое:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Image Preloader Progress Bar</title>
<link rel="stylesheet" href="./style.css">
</head>
<style type="text/css">
button {
font-size: 100%;
padding: 5px 10px 10px 10px;
margin-top: 10%;
}
</style>
<body>
<!-- кнопка для запуска -->
<button value="Получить предсказание по крипте" onclick="tested();">Получить прогноз по криптовалюте</button>
<!-- наш прогресс-бар -->
<div class="loader">
<div class="progress-bar">
<div class="progress-stripes"></div>
<div class="percentage">0%</div>
</div>
</div>
<span>Система готова</span>
<p>
<!-- тут будет финальный текст -->
</p>
<!-- подключаем jQuery и сам скрипт -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
<script src="./script.js"></script>
</body>
</html>
Мы добавили кнопку и раздел с тегом <p> для финального текста. Всё остальное донастроим в скрипте.
Запуск по кнопке
В исходном проекте прогресс-бар начинает работать автоматически сразу после загрузки страницы. Нам такое не нужно, а нужно, чтобы всё запускалось после нажатия на кнопку. Поэтому снова правим скрипт.
Убираем автозагрузку. За это отвечает свойство $(window).load(function()
. Находим его и полностью удаляем эту функцию.
Привязываем запуск к кнопке. Помните, мы правили функцию function preload(imgArray)
? Именно она отвечает за работу всего прогресс-бара. Чтобы привязать её к кнопке, оборачиваем вызов функции в такой код:
function launch() {
preload(demoImgArray);
}
Смотрим, чтобы в описании кнопки на странице тоже всё было правильно:
<button value="Получить предсказание по крипте"
<strong>onclick="launch()</strong>
;">Получить прогноз по криптовалюте</button>
Строка onclick="launch()"
означает, что при нажатии на кнопку сработает функция launch()
, а именно она у нас в скрипте отвечает за запуск прогресс-бара.
Делаем кнопку одноразовой. Чтобы всё работало без сбоев и предсказуемо, давайте запретим нажимать на кнопку больше одного раза. Для этого используем jQuery в функции preload(imgArray)
:
$('button').prop('disabled', true);
Этот код делает следующее:
- Находит на странице элемент <button>.
- Присваивает свойству кнопки disabled значение true. Это значит, что кнопка становится неактивной — она видна, но нажать на неё нельзя. То что нужно.
Что в результате
Мы разместили свою версию проекта на сайте — можно пользоваться самому и делиться с друзьями. Если вы хотите сделать себе такое же и даже круче — держите исходный код:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Image Preloader Progress Bar</title>
<link rel="stylesheet" href="./style.css">
</head>
<style type="text/css">
button {
font-size: 100%;
padding: 5px 10px 10px 10px;
margin-top: 10%;
}
</style>
<body>
<!-- кнопка для запуска -->
<button value="Получить предсказание по крипте" onclick="launch();">Получить прогноз по криптовалюте</button>
<!-- наш прогресс-бар -->
<div class="loader">
<div class="progress-bar">
<div class="progress-stripes"></div>
<div class="percentage">0%</div>
</div>
</div>
<span>Система готова</span>
<p>
<!-- тут будет финальный текст -->
</p>
<!-- подключаем jQuery и сам скрипт -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
<script src="./script.js"></script>
</body>
</html>
body {
background: #e7e7e7;
font-family: Arial, Helvetica, Sans-serif;
text-align: center;
padding: 5px;
margin: 0;
}
.loader {
margin: 50px auto 10px;
width: 300px;
height: 25px;
border-radius: 14px;
border-bottom: 1px solid #fff;
border-top: 1px solid #999;
background: #ccc;
overflow: hidden;
position: relative;
}
.loader.gray {
background: #999;
}
.progress-bar {
height: inherit;
width: 0%;
border-radius: inherit;
position: relative;
overflow: hidden;
}
.progress-stripes {
width: inherit;
height: inherit;
font-size: 180px;
font-weight: bold;
margin-top: -50px;
letter-spacing: -14px;
}
.percentage {
position: absolute;
top: 4px;
right: 5px;
font-weight: bold;
font-size: 16px;
}
/***************************************/
/* BELOW HERE IS SOLELY FOR AESTHETICS */
/*_____________________________________*/
/*** COLOR SCHEMES ***/
/* RED */
.red .progress-bar {
background: #e74c3c;
}
.red .progress-stripes {
color: #c0392b;
}
.red .percentage {
color: #eee;
}
/* BLUE */
.blue .progress-bar {
background: #3498db;
}
.blue .progress-stripes {
color: #2980b9;
}
.blue .percentage {
color: #eee;
}
/* GREEN */
.green .progress-bar {
background: #2ecc71;
}
.green .progress-stripes {
color: #27ae60;
}
.green .percentage {
color: #fff;
}
/* YELLOW */
.yellow .progress-bar {
background: #f1c40f;
}
.yellow .progress-stripes {
color: #f39c12;
}
.yellow .percentage {
color: #fff;
}
/* PURPLE */
.purple .progress-bar {
background: #9b59b6;
}
.purple .progress-stripes {
color: #8e44ad;
}
.purple .percentage {
color: #eee;
}
/* GRAY */
.gray .progress-bar {
background: #ecf0f1;
}
.gray .progress-stripes {
color: #bdc3c7;
}
.gray .percentage {
color: #333;
}
/*** LOADED ***/
span {
color: #888;
text-shadow: 0 1px 0 #fff;
}
span.loaded.red {
color: #c0392b;
}
span.loaded.blue {
color: #2980b9;
}
span.loaded.green {
color: #27ae60;
}
span.loaded.yellow {
color: #d35400;
}
span.loaded.purple {
color: #8e44ad;
}
span.loaded.gray {
color: #444;
}
/*** SKINS ***/
.skins {
padding: 4px 0 8px;
cursor: default;
font-size: 14px;
color: #666;
background: #fff;
opacity: 0.5;
-moz-transition: opacity 0.25s linear;
-webkit-transition: opacity 0.25s linear;
transition: opacity 0.25s linear;
}
.skins:hover {
opacity: 1;
}
.skin {
width: 20px;
height: 20px;
cursor: pointer;
margin-bottom: -7px;
border: 1px solid #fff;
display: inline-block;
*display: inline;
zoom: 1;
}
#red {
background: #c0392b;
}
#red:hover {
background: #e74c3c;
}
#blue {
background: #2980b9;
}
#blue:hover {
background: #3498db;
}
#green {
background: #27ae60;
}
#green:hover {
background: #2ecc71;
}
#yellow {
background: #f39c12;
}
#yellow:hover {
background: #f1c40f;
}
#purple {
background: #8e44ad;
}
#purple:hover {
background: #9b59b6;
}
#gray {
background: #7f8c8d;
}
#gray:hover {
background: #95a5a6;
}
// этапы в прогресс-баре
var s1 = ['Собираю данные из интернета', 'Читаю Википедию', 'Смотрю паблики во ВКонтакте'];
var s20 = ['Обучаю нейросеть', 'Анализирую увиденное', 'Выделяю важное'];
var s40 = ['Готовлю технический анализ', 'Думаю о будущем', 'Осознаю важность момента'];
var s60 = ['Постулирую неведомое', 'Инкапсулирую полиморфизм', 'Кристаллизую суть'];
var s80 = ['Тыкаю пальцем в небо', 'Формализую данность как класс', 'Удаляю противоречия и вношу ясность'];
var result = ['Сначала крипта подрастет, потом обвалится. Потом снова подрастет, потом снова обвалится.', 'Можно закупать, хотя лучше сначала продать, но если важнее купить, то можно и не продавать', 'По всем показателям рецессия будет следовать за отскоком, но это вряд ли отразится на текущей ситуации, поэтому необходимо обратить внимание на волатильность.'];
// генератор случайных чисел в диапазоне от минимального до максимального
function randz(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// готовим итоговые значения
var s1_final = s1[randz(0, 2)];
var s20_final = s20[randz(0, 2)];
var s40_final = s40[randz(0, 2)];
var s60_final = s60[randz(0, 2)];
var s80_final = s80[randz(0, 2)];
var percent_final = randz(60, 99);
var result_final = result[randz(0, 2)];
/* SET RANDOM LOADER COLORS FOR DEMO PURPOSES */
var demoColorArray = ['red', 'blue', 'green', 'yellow', 'purple', 'gray'];
var colorIndex = Math.floor(Math.random() * demoColorArray.length);
setSkin(demoColorArray[colorIndex]);
var yes = true;
/* RANDOM LARGE IMAGES FOR DEMO PURPOSES */
var demoImgArray = ['http://www.hdwallpapers.in/walls/halloween_2013-wide.jpg', 'http://www.hdwallpapers.in/walls/2013_print_tech_lamborghini_aventador-wide.jpg', 'http://www.hdwallpapers.in/walls/ama_dablam_himalaya_mountains-wide.jpg', 'http://www.hdwallpapers.in/walls/arrow_tv_series-wide.jpg', 'http://www.hdwallpapers.in/walls/anna_in_frozen-wide.jpg', 'http://www.hdwallpapers.in/walls/frozen_elsa-wide.jpg', 'http://www.hdwallpapers.in/walls/shraddha_kapoor-wide.jpg', 'http://www.hdwallpapers.in/walls/sahara_force_india_f1_team-HD.jpg', 'http://www.hdwallpapers.in/walls/lake_sunset-wide.jpg', 'http://www.hdwallpapers.in/walls/2013_movie_cloudy_with_a_chance_of_meatballs_2-wide.jpg', 'http://www.hdwallpapers.in/walls/bates_motel_2013_tv_series-wide.jpg', 'http://www.hdwallpapers.in/walls/krrish_3_movie-wide.jpg', 'http://www.hdwallpapers.in/walls/universe_door-wide.jpg', 'http://www.hdwallpapers.in/walls/night_rider-HD.jpg', 'http://www.hdwallpapers.in/walls/tide_and_waves-wide.jpg', 'http://www.hdwallpapers.in/walls/2014_lamborghini_veneno_roadster-wide.jpg', 'http://www.hdwallpapers.in/walls/peeta_katniss_the_hunger_games_catching_fire-wide.jpg', 'http://www.hdwallpapers.in/walls/captain_america_the_winter_soldier-wide.jpg', 'http://www.hdwallpapers.in/walls/puppeteer_ps3_game-wide.jpg', 'http://www.hdwallpapers.in/walls/lunar_space_galaxy-HD.jpg', 'http://www.hdwallpapers.in/walls/2013_wheelsandmore_lamborghini_aventador-wide.jpg', 'http://www.hdwallpapers.in/walls/destiny_2014_game-wide.jpg', 'http://www.hdwallpapers.in/colors_of_nature-wallpapers.html', 'http://www.hdwallpapers.in/walls/sunset_at_laguna_beach-wide.jpg'];
// Stripes interval
var stripesAnim;
var calcPercent;
$progress = $('.progress-bar');
$percent = $('.percentage');
$stripes = $('.progress-stripes');
$stripes.text('////////////////////////');
/* CHANGE LOADER SKIN */
$('.skin').click(function () {
var whichColor = $(this).attr('id');
setSkin(whichColor);
});
// Call function to load array of images
function launch() {
preload(demoImgArray);
}
// Call function to animate stripes
stripesAnimate();
/*** FUNCTIONS ***/
/* LOADING */
function preload(imgArray) {
$('button').prop('disabled', true);
var increment = Math.floor(100 / imgArray.length);
var i = 0;
while (i < 300) {
$progress.animate({
width: "+=" + 0.25 + "%"
}, 100);
i = i + 1;
}
calcPercent = setInterval(function () {
//loop through the items
$percent.text(Math.floor(($progress.width() / $('.loader').width()) * 100) + '%');
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 1) {
$('span').text(s1_final);
}
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 20) {
$('span').text(s20_final);
}
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 40) {
$('span').text(s40_final);
}
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 60) {
$('span').text(s60_final);
}
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 80) {
$('span').text(s80_final);
}
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 100) {
yes = false;
$('span').text('Примерная точность прогноза: ' + percent_final + '%');
$('p').text(result_final);
}
});
}
/* STRIPES ANIMATION */
function stripesAnimate() {
if (yes) {
animating();
stripesAnim = setInterval(animating, 2500);
}
}
function animating() {
if (yes) {
$stripes.animate({
marginLeft: "-=30px"
}, 2500, "linear").append('/');
}
}
function setSkin(skin) {
$('.loader').attr('class', 'loader ' + skin);
$('span').hasClass('loaded') ? $('span').attr('class', 'loaded ' + skin) : $('span').attr('class', skin);
}
В этом коде много лишних строк, он неоптимальный и его можно существенно улучшить. Но главное — он уже работает, и его можно быстро допилить под свои задачи. Поэтому вот домашнее задание для тех, кто хочет идеал:
- найти и убрать лишние функции;
- заменить настоящую загрузку картинок (посмотрите внимательно код) на что-то не настолько нагрузочное;
- сделать больше вариантов текста на каждом этапе.