Псевдо-случайное изображение (на примере страницы 404ой ошибки)

от автора

Однажды автор этого поста работал над одним заказом по разработке простенько сайта и тогда появилась идея — придать всем страницам некой уникальности и запоминаемости — использовать уникальные фоновые текстуры или элементы дизайна (активно использовался parallax-scrolling). Так как в тот момент дедлайн был довольно близок, а идея — в зачаточном состоянии, было реализовано намного проще — простыми заготовками, но идея выброшена не была.

Спустя некоторое время случайно наткнулся на мертвую ссылку, которая вела на несуществующий Tumblr-блог, и страница ошибки сразу привлекла внимание. Обновив страничку фоновое изображение (в виде gif-анимации) сменилось — внимание ещё более усилилось. Почитав исходники стало понятно что все изображения «прописаны» статично, но это натолкнуло на другую идею, о которой вы узнаете под катом.

Идея заключалась в следующем: «Почему бы нам в случае, когда необходимо оформить какую-либо страницу (в частности сервисную — вход, выход, ошибка), или просто получить тематическое изображение для оформления контента, не использовать псевдо-случайные изображения?»
Семантически под «псевдо-случайными» я имею в виду изображения определенной тематики (или имеющие между собой какие-либо общие черты), но с течением времени результат «выпадения» был бы в той или иной степени уникальным.

Возможные методы решения:

  • Парсинг результатов поиска (google, yandex) по картинкам;
  • Парсинг хостингов картинок, имеющие деление изображений по тегам или критериям;
  • Инстаграм и сервисы иже с ним;
  • Использовать средства блог-платформ, имеющих акцент на фото-контент.

Парсинг результатов поисковых запросов отпал по причинам встречающейся низкой релевантности, большого количества «мусора», а сами изображения хранятся черт знает где. Хостинг картинок — как-то не сложилось (может быть и зря) сразу. Инстаграм — низкое качество изображений (640х640 точек) и сложность в запросах для получения релевантных ответов. Так и остался крайний вариант — блог-платформы.

Не скажу что выбор был мучительный, так как сам на Tumblr веду пару блогов и в курсе относительно статистики. В том числе — статистики постов:

Плюсы данного решения:

  • Изображения в тематических блогах придерживаются своего концепта в 9 из 10 случаев;
  • При наличии корпоративного или личного блога на этом же сервисе изображения можно брать прямо из него, получается довольно прикольно;
  • Нет необходимости беспокоиться об актуальности;
  • Изображения находятся в открытом доступе;
  • Tumblr отлично дружит с ifttt.

Минусы:

  • Если брать контент не у блога с устоявшимся форматом, есть вероятность получить изображение лысого мужика в наколках не соответствующее формату;

Теперь остается дело за малым — получить сами картинки. Хочется отдельно выразить благодарность разработчикам этой платформы, так как апи для получения и выборки контента очень прост и качественно реализован. Работу по получению и разбору данных было решено возложить на клиента (что без каких-либо сложностей переписывается на любой серверный язык). В итоге у меня получился следующий пример (дабы сократить длину поста css обернут в спойлер):

<!DOCTYPE html> <html> <head>     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />     <meta name="description" content="404 | Page Not Found" />     <title>404 | Page Not Found</title>     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />     <link rel="shortcut icon" href="./blank-favicon.ico" />     <link href="//fonts.googleapis.com/css?family=PT+Sans+Narrow&subset=latin,cyrillic" rel="stylesheet" type="text/css" />     <style type="text/css"> 

CSS (нажмите для раскрытия)

    * {         margin:0;         padding:0     }     html,body{         min-height: 100%;         height: 100%;         min-width: 100%;         background-color: #000;         overflow: hidden;     }         body{         position:fixed;         font-family: 'PT Sans Narrow',Helvetica,Arial,Verdana,sans-serif;         visibility:visible;         top:0;         right:0;         left:0;         -webkit-font-smoothing:antialiased     }     #bg-fullscreen {         position: absolute;         -moz-opacity: 0;         -khtml-opacity: 0;         opacity: 0;         top: 0;         left: 0;         width: 100%;         height: 100%;         background-size: cover;         background-position: 50% 50%;         -webkit-transition: opacity 2s ease-in-out;            -moz-transition: opacity 2s ease-in-out;             -ms-transition: opacity 2s ease-in-out;              -o-transition: opacity 2s ease-in-out;                 transition: opacity 2s ease-in-out;                          -webkit-filter: blur(3px);            -moz-filter: blur(3px);              -o-filter: blur(3px);             -ms-filter: blur(3px);                 filter: blur(3px);     }         #bg-fullscreen.show {             -moz-opacity: 0.9;             -khtml-opacity: 0.9;             opacity: 0.9;         }     #content {         position: absolute;         top: 0;         left: 0;         width: 100%;         height: 100%;         text-align: center;     }         #content * {             color: #fff;         }         #content h1 {             font-size: 20em;             text-shadow: 0px 0px 42px rgba(0, 0, 0, 1);         }         #content h3 {             font-size: 5.4em;             position: relative;             top: -0.9em;             text-shadow: 0px 0px 22px rgba(0, 0, 0, 1);             -moz-opacity: 0.9;             -khtml-opacity: 0.9;             opacity: 0.9;         }         #content div.link{             position: absolute;             bottom: 80px;             text-align: center;             width: 100%;         }             #content div a {                 display: inline-block;                 font-size: 3em;                             position: relative;                                  padding: 0 30px 5px 30px;                 background-color: #d63a0a;                 color: #fff;                 text-decoration: none;                                  -webkit-box-shadow: 0px 0px 30px 0px rgba(0, 0, 0, 0.6);                    -moz-box-shadow: 0px 0px 30px 0px rgba(0, 0, 0, 0.6);                         box-shadow: 0px 0px 30px 0px rgba(0, 0, 0, 0.6);             }                 #content a:hover {                     top: -1px;                 }                 #content a:active {                     top: +2px !important;                 }             #content a.home {}          @media only screen and (max-width: 1280px) {             #content h1 {                 font-size: 13em;             }                          #content h3 {                 font-size: 3.8em;             }                          #content div a {                 font-size: 2em;                         }         }                  @media only screen and (max-width: 479px) {             #content h1 {                 font-size: 10em;             }                          #content h3 {                 font-size: 2.8em;             }                          #content div a {                 font-size: 1.4em;                         }         } 

    </style>     <noscript>         <style type="text/css">             #bg-fullscreen {                 -moz-opacity: 0.9;                 -khtml-opacity: 0.9;                 opacity: 0.9;                 background-image: url('//habrastorage.org/files/7c1/dfc/c33/7c1dfcc3386347d0aa20b4f3cc1a410a.jpg');             }         </style>     </noscript>     <script type="text/javascript" src="//code.jquery.com/jquery-latest.min.js"></script>     <script type="text/javascript">     $(document).ready(function (){         var imagesArray = [],             debug = true;         function getImagesFromTumblr(blogName, imgArr, imgCount, callback, makeOffset){             var offsetStep = 20,                 makeOffset = typeof makeOffset !== 'undefined' ? makeOffset : 0,                 imgCount = typeof imgCount !== 'undefined' ? imgCount : 5;             $.ajax({                 type: 'GET',                 // https://www.tumblr.com/docs/en/api/v2                 url : '//api.tumblr.com/v2/blog/'+ blogName +'.tumblr.com/posts',                 dataType: 'jsonp',                 data: {                     // https://www.tumblr.com/oauth/apps                     api_key: 'P1M2xgqzN8Q5V9Oh1eMp2a6V2YceKV5Z7FvlPZlWgDXvPT6AMs',                     offset:  makeOffset                                      }, success: function (data) {                     if(debug) console.log('Makeing request with offset = %d', makeOffset);                     if(data.meta.status === 200) { // if answer is 'ok'                         $.each(data.response.posts, function(){                             if(this.type === 'photo') {                                 $.each(this.photos, function(){                                     var ext = this.original_size.url.split('.').pop(); // find image extension                                     if(                                         // check image for:                                         (ext === 'jpg') // 1. type - 'jpg'                                         && (this.original_size.width >= 640) // 2. minimal width                                         //&& (this.original_size.width > this.original_size.height) // 2. horizontal                                     ) {                                         if(imgArr.length < imgCount) {                                             imgArr.push(this);                                         }                                     }                                 });                             }                         });                     }                     // if array not full..                     if(imgArr.length < imgCount)                         // ..make a recrussive run                         getImagesFromTumblr(                             blogName,                              imgArr,                              imgCount,                              callback,                             ((makeOffset === 0) ? offsetStep : makeOffset + offsetStep)                         )                     else                         if($.isFunction(callback)) callback(true);                                      }, error: function () {                 if(debug) console.error('Error try ajax request');                 if($.isFunction(callback)) callback(false);             }});         }                  // 'womenexcellence' - girls, +18         // 'life'            - black'n'white photos         // 'weirdvintage'    - weird vintage         // 'awesomepeoplehangingouttogether' - awesome people hanging out together         // 'meiguiceserra'   - space planets                  if(debug) console.time('Getting Tumblr Images Data');         getImagesFromTumblr('awesomepeoplehangingouttogether', imagesArray, 10, function(noerror){             if(debug) console.timeEnd('Getting Tumblr Images Data');             function getArrayItem(arr) {                 return arr[Math.floor(Math.random() * arr.length)];             }             function preloadImg(url, callback) {                 var pImg = new Image();                 pImg.onload = function() {                     if($.isFunction(callback)) callback(true);                 }                 pImg.src = url;             }              if(debug) console.log(imagesArray);             if(imagesArray.length > 0) {                                  var imageUrl = getArrayItem(imagesArray).original_size.url;                 if(debug) console.log('Random image url: %s', imageUrl);                                  if(debug) console.time('Image downloading');                 preloadImg(imageUrl, function(){                     if(debug) console.timeEnd('Image downloading');                     $('#bg-fullscreen').css({                         'background-image': 'url('+ imageUrl +')'}).addClass('show');                 });             }         });              });     </script>     </head>     <body>         <div id="bg-fullscreen"></div>         <div id="content">             <h1>404</h1>             <h3>Not found</h3>             <div class="link">                 <a href="" class="home">&larr; Main page</a>             </div>         </div>     </body> </html> 

Алгоритм работы функции следующий:

  1. Формируем и отправляем Ajax-запрос к API Tumblr-a;
  2. Проверяем статус ответа и проходимся по каждому посту;
  3. Если это фото-пост, то проходимся по каждому изображению;
  4. Если изображение нам подходит (например — тип, минимальный размер, соотношение сторон), то добавляем его в итоговый массив;
  5. Если по завершению прохода нужное количество изображений не собрано — рекурсивно запускаемся снова, но с новым отступом.

Результат работы примера выглядит следующим образом (одно изображение — один показ):

404 Pages Slide Show

И несколько слов о том, в каком виде у нас возвращаемые данные:

Плюсы данной реализации:

  • Если захочется использовать gif-изображение — изменяем искомое расширение (строка ~178) и пересматриваем проверку размеров изображений;
  • Чтобы изменить источник изображений — необходимо изменить один вызов функции;
  • При отключенном JavaScript — выведем изображение из заготовки (см. <noscript>… </noscript>);
  • Доступны различные размеры изображений;
  • Работает даже в IE6 (при выключенном ‘debug’ — режиме, строка ~153);
  • Легко «допилить» под себя.

И минусы:

  • В среднем получение и разбор данных (получалось 1..2 запроса, 10 изображений) во время тестов занимал порядка 0,4..1 секунды, что довольно долго;
  • Необходимость таскать JQuery.

Просмотреть демонстрацию

Эпилог

Данный метод может замечательно вписаться в небольшие сайты, портфолио, студии, блоги. Не нуждается в поддержке, легко интегрируется в готовые решения, не нагружает сервер. Вполне реально использовать в шаблонах для наполнения тестовым контентом (несколько строк на jQuery по замене ‘src’ у <img />). Буду рад, если кому-то помог, или навел на другую стоящую мысль.

ссылка на оригинал статьи http://habrahabr.ru/post/229449/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *