<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Сергей Чикуёнок</title>
	<atom:link href="http://chikuyonok.ru/feed/" rel="self" type="application/rss+xml" />
	<link>http://chikuyonok.ru</link>
	<description>веб-разработчик</description>
	<lastBuildDate>Sun, 24 Feb 2013 20:03:48 +0000</lastBuildDate>
	<language>ru-RU</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.5.1</generator>
		<item>
		<title>Создание профессиональных сайтов с помощью DocPad</title>
		<link>http://chikuyonok.ru/2013/02/docpad/</link>
		<comments>http://chikuyonok.ru/2013/02/docpad/#comments</comments>
		<pubDate>Sun, 24 Feb 2013 20:03:48 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Прочее]]></category>

		<guid isPermaLink="false">http://chikuyonok.ru/?p=798</guid>
		<description><![CDATA[Продолжаю знакомить с open-source наработками, созданными в процессе работы над проектом Emmet. В прошлый раз это был CodeMirror Movie, а в этот раз познакомлю вас с процессом создания сайта документации на основе DocPad. tl;dr – Как сделать профессиональный высокопроизводительный сайт на DocPad Используйте плагин docpad-plugin-menu для автоматической генерации меню сайта. Используйте grunt-frontend и docpad-plugin-frontend для [...]]]></description>
				<content:encoded><![CDATA[<p>Продолжаю знакомить с open-source наработками, созданными в процессе работы над проектом Emmet. В <a href="/2013/02/codemirror-movie/">прошлый раз</a> это был <a href="https://github.com/sergeche/codemirror-movie">CodeMirror Movie</a>, а в этот раз познакомлю вас с процессом создания сайта документации на основе DocPad.</p>
<hr />
<h2>tl;dr – Как сделать профессиональный высокопроизводительный сайт на DocPad</h2>
<ul>
<li>Используйте плагин <a href="https://github.com/sergeche/docpad-plugin-menu">docpad-plugin-menu</a> для автоматической генерации меню сайта.</li>
<li>Используйте <a href="https://github.com/sergeche/grunt-frontend">grunt-frontend</a> и <a href="https://github.com/sergeche/docpad-plugin-frontend">docpad-plugin-frontend</a> для сборки CSS и JS ресурсов и правильного кэширования.</li>
<li>Создайте специальное debug-окружение для плагина <code>docpad-plugin-frontend</code> для поиска проблем в исходниках CSS и JS, а не их минифицированных версиях.</li>
<li>Настройте веб-хуки на GitHub и <a href="http://weblog.bocoup.com/introducing-gith-github-webhooks-for-node/">Gith</a> на сервере для автоматической сборки сайта после каждого коммита.</li>
<li>Настройке nginx для правильного кэширования статических файлов и экономии ресурсов процессора.</li>
</ul>
<hr />
<p><a href="https://docpad.org">DocPad</a> — это генератор статических сайтов, написанный на CoffeeScript. В отличие от сайтов, созданных с использованием обычных CMS вроде Django, Drupal и WordPress, статические сайты потребляют минимальное количество серверных ресурсов, так как представляют собой набор заранее сгенерированных обычных HTML-файлов. То есть кроме обычного веб-сервера вроде nginx или Apache для работы сайта ничего не нужно.</p>
<p>Для <a href="http://docs.emmet.io">документации Emmet</a> это идеальный вариант, позволяющий не только удобно работать над сайтом, но и значительно сократить расходы на хостинг: можно выдерживать относительно высокую посещаемость даже на недорогом хостинге.</p>
<p>Но у DocPad, как и у многих других генераторов, есть ряд недостатков, не позволяющих делать по-настоящему профессиональные и быстрые сайты. Их я и решил исправить, написав несколько плагинов:</p>
<ul>
<li><a href="https://github.com/sergeche/docpad-plugin-menu">docpad-plugin-menu</a> — автоматическая генерация меню для сайта.</li>
<li><a href="https://github.com/sergeche/grunt-frontend">grunt-frontend</a> — «умная» сборка CSS и JS файлов.</li>
<li><a href="https://github.com/sergeche/docpad-plugin-frontend">docpad-plugin-frontend</a> – вывод собранных с помощью <code>grunt-frontend</code> CSS и JS файлов с учётом правильного кэширования, а также управление наборами файлов между шаблонами.</li>
</ul>
<p>Если вы ещё не знакомы с DocPad, рекомендую вам посмотреть и прочитать <a href="https://docpad.org/docs/intro">Введение в DocPad</a>, чтобы вы представляли, о чём в дальнейшем пойдёт речь.</p>
<h3>Генерация меню</h3>
<p>Плагин <a href="https://github.com/sergeche/docpad-plugin-menu">docpad-plugin-menu</a> умеет геренировать структурированное меню для всех страниц сайта (то есть для всех файлов из папки <code>src/documents</code>). Этот плагин добавляет метод <code>generateMenu(url)</code> в объект <code>templateData</code>, в контексте которого отрисовываются все шаблоны проекта. На вход этот метод принимает URL страницы, относительно которого нужно создать меню, на выходе вы получите структуру разделов сайта, которую удобно отрисовывать, например, с помощью <a href="https://github.com/docpad/docpad-plugin-partials/">partials</a>.</p>
<p>Подробнее о возможностях плагина и примерах его использования читайте на <a href="https://github.com/sergeche/docpad-plugin-menu#readme">основной странице проекта</a>.</p>
<h3>Сборка фронт-энд ресурсов</h3>
<p>Для удобства разработки я разбиваю CSS и JS файлы на отдельные модули, которые затем склеиваются и минифицируются – это стандартная практика высокопроизводительных сайтов. Для сборки я использую <a href="http://gruntjs.com">Grunt.js</a> в котором, казалось бы, уже есть все необходимые инструменты для выполнения этих задач.</p>
<p>Но и тут я не нашёл ничего подходящего. Дело в том, что мне важна <em>дата последнего обновления</em> минифицированного файла, потому что я хочу её подставлять в URL файла для эффективного сброса кэша. Поэтому обновлять конечный файл нужно только тогда, когда поменялся один исходных файлов.</p>
<p>Для решения этой задачи я написал свой сборщик: <a href="https://github.com/sergeche/grunt-frontend">grunt-frontend</a>. Работает он следующим образом. Во время конкатенации и минификации нескольких файлов в один он записывает структуру исходных файлов и их md5-отпечаток в специальный файл <code>.build-catalog.json</code>. При следующей сборке плагин смотрит на структуру и содержимое исходных файлов: если ничего не поменялось, то и конечный файл не минифицируется и не обновляется.</p>
<p>Это не только сокращает время сборки, но и позволяет сохранить такие важные данные конечного файла как дату обновления и md5-отпечаток. Все эти данные хранятся в <code>.build-catalog.json</code>, его желательно хранить вне версионного контроля.</p>
<p>Для минификации используются библиотеки <a href="https://github.com/css/csso">CSSO</a> (с автоматическим инлайнингом всех подключённых через <code>@import</code> файлов) и <a href="https://github.com/mishoo/UglifyJS">UglifyJS</a>.</p>
<p><a href="https://github.com/sergeche/grunt-frontend">Подробнее об использовании <code>grunt-frontend</code></a>.</p>
<h3>Управление CSS и JS ресурсами</h3>
<p>Очень часто возникает необходимость управлять подключением CSS и JS файлов на различных страницах сайта. Скажем, на всех страницах нужно использовать набор файлов <code>set1</code>; для всех внутренних страниц раздела <code>/about/</code> нужно дополнительно использовать <code>set2</code> и <code>set3</code>, но для страницы <code>/about/contacts/</code> вместо <code>set2</code> нужно использовать <code>set4</code> (то есть <code>set1</code>, <code>set4</code>, <code>set3</code>, именно в таком порядке). Кроме того, в URL всех ресурсов  нужно подставлять дату модификации файла чтобы эффективно сбрасывать кэш.</p>
<p>Для решения этих задач был написан плагин <a href="https://github.com/sergeche/docpad-plugin-frontend">docpad-plugin-frontend</a>. Он добавляет метод <code>assets(prefix)</code>, который позволяет доставать отсортированный список ресурсов из текущего документа и всей цепочки шаблонов, применяемых к документу. Если в корневой папке проекта существует файл <code>.build-catalog.json</code>, то плагин считывает его и возвращает список ресурсов с префиксом в виде даты модификации файла.</p>
<p>Например, описанную выше задачу с управлением наборов ресурсов можно решить следующим образом. Для основного шаблона <code>default.html.eco</code> указываем основной набор файлов в мета-данных:</p>
<pre>
---
js: "/js/fileA.js"
---
</pre>
<p>В шаблоне <code>about.html.eco</code>, который наследуется от основного шаблона и применяется ко всем документам <code>/about/*</code>, указываем следующие данные:</p>
<pre>
---
layout: default
js2: ["/js/fileB.js", "/js/fileC.js"]
js3: ["/js/fileD.js", "/js/fileE.js"]
---
</pre>
<p>В документе <code>/about/contacts/index.html</code> перекрываем набор <code>js2</code>:</p>
<pre>
---
layout: about
js2: "/js/contacts.js"
---
</pre>
<p>Теперь, при рендеринге страницы <code>/about/contacts/index.html</code>, вызов <code>assets('js')</code> вернёт следующий набор файлов:</p>
<ul>
<li><code>/js/fileA.js</code></li>
<li><code>/js/contacts.js</code></li>
<li><code>/js/fileD.js</code></li>
<li><code>/js/fileE.js</code></li>
</ul>
<p>Как видите, всё довольно просто: придумываем префикс для категории ресурсов, а сами наборы создаём с помощью числовых суффиксов. Далее вызываем <code>assets()</code> в шаблоне и передаём ему префикс набора ресурсов: файлы сортируются по суффиксу в порядке возрастания; наборы с одинаковым суффиксом перекрываются.</p>
<p>Более подробную информацию о возможностях плагина и примерах использования читайте на <a href="https://github.com/sergeche/docpad-plugin-frontend#readme">главной странице репозитория</a>.</p>
<h3>Режим отладки</h3>
<p>Очень часто бывает так, что пользователь вашего сайта сообщает вам, что в каком-то браузере возникает ошибка: не работает JavaScript или элементы наехали друг на друга. Но весь ваш CSS и JS код минифициорван и вам довольно сложно найти то самое место в <em>исходных файлах</em>, где эта ошибка возникает.</p>
<p>В будущем эти проблемы можно будет находить с помощью <a href="http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/">Source Maps</a>, но сейчас далеко не все минификаторы и браузеры их поддерживают.</p>
<p>В плагине <code>docpad-plugin-frontend</code> есть специальный режим отладки. Так как структура всех минифицированных файлов хранится в JSON-каталоге, нам не составит труда при необходимости вывести список исходных файлов вместо скомпилированного.</p>
<p>Для этого в DocPad я создаю отдельное <em>окружение</em>, в котором указываю опцию <code>frontendDebug: true</code>. Если опция <code>frontendDebug</code> равна <code>true</code>, то метод <code>assets()</code> плагина <code>docpad-plugin-frontend</code> будет по возможности возвращать список исходных файлов вместо минифицированных. Пример настройки <code>docpad.coffee</code>:</p>
<pre class="brush: js">
module.exports = {
    …
    environments:
        debug:
            frontendDebug: true
}
</pre>
<p>Теперь при запуске <code>DocPad</code> в окружением <code>debug</code>, вы получите HTML-страницы с исходными CSS и JS файлами и сможете легко найти ошибку:</p>
<pre><code>docpad run --env=debug</code></pre>
<h3>Автоматический деплой с GitHub</h3>
<p>Я настроил сервер таким образом, чтобы после каждого коммита в ветку <code>master</code> сайт автоматически генерировался.</p>
<p>Со стороны GitHub я использовал обычный “WebHook”, а на стороне сервера – <a href="http://weblog.bocoup.com/introducing-gith-github-webhooks-for-node/">Gith</a>.</p>
<p>Gith – это удобный веб-сервер для Node.JS, который умеет принимать и фильтровать данные веб-хуков GitHub. Мой сервер, который запускает сборку сайта, выглядит следующим образом:</p>
<pre class="brush: js">
var childProc = require('child_process');
var path = require('path');

var gith = require('gith').create(3000);

gith({
    // Слушаем хуки только для ветки "master"
    branch: 'master'
}).on('all', function(payload) {
    console.log('Run deply script on', new Date());

    // Запускаем скрипт сборки сайта
    var deploy = childProc.spawn('sh', ['/web/deploy.sh']);

    deploy.stdout.on('data', function(data) {
        var message = data.toString('utf8');

        if (~message.indexOf('subscribe')) {
            // Docpad может спросить про подписку на рассылку, откажемся
            deploy.stdin.write('n');
        } else if (~message.toLowerCase().indexOf('privacy')) {
            // Docpad может спросить про политику безопасности, согласимся
            deploy.stdin.write('y');
        }
    });

    deploy.stderr.on('data', function(data) {
        console.log('Error: ', data.toString('utf8'));
    });

    deploy.on('exit', function(code) {
        console.log('Deploy complete with exit code ' + code);
    });
});
</pre>
<p>Сам скрипт сборки проекта <code>deploy.sh</code> выглядит следующим образом:</p>
<pre><code>
#! /usr/bin/env bash
git pull
git submodule foreach 'git checkout master &amp;&amp; git pull origin master'
npm install
grunt
docpad generate
find ./out -type f ( -name '*.html' -o -name '*.css' -o -name '*.js' )  -exec sh -c "gzip -7 -f &lt; {} &gt; {}.gz" ;
</code></pre>
<h3>Настройка nginx</h3>
<p>В качестве веб-сервера я использую <a href="http://nginx.org">nginx</a>, который прекрасно оптимизирован для отдачи статики. В настройках сайта нам нужно сделать следующее:</p>
<ul>
<li>Прописать рерайты для статических файлов: отсекать дату модификации в начале пути и посылать правильные кэширующие заголовки.</li>
<li>Отдавать статику в gzip для снижения объёма передаваемых файлов.</li>
</ul>
<p>Если посмотреть на скрипт <code>deploy.sh</code>, то вы увидите, что в последнем шаге создаются gzip-версии всех HTML, CSS и JS файлов. У nginx есть специальный модуль <a href="http://wiki.nginx.org/HttpGzipStaticModule">HttpGzipStaticModule</a>, который может отдавать заранее созданные gzip-версии файлов вместо автоматической генерации для каждого запроса. Этот трюк позволит нам сэкономить процессорные ресурсы. Для того, чтобы воспользоваться этим модулем, его нужно добавить в nginx при компиляции:</p>
<pre><code>./configure --with-http_gzip_static_module</code></pre>
<p>Мой конфиг nginx выглядит так:</p>
<pre><code>
server {
    server_name  your-server.com;
    root         /path/to/web-site/out;

    index  index.html index.htm;

    # отсекаем дату модификации со статических ресурсов
    location ~* ^/d+/(css|js)/ {
        rewrite ^/(d+)/(.*)$ /$2;
    }

    # кэшируем всю статику
    location ~* .(ico|css|js|gif|jpe?g|png)$ {
        expires max;
        access_log off;
        add_header Pragma public;
        add_header Cache-Control "public";
    }

    # включаем поддержку статических gzip-версий файлов
    gzip_static on;
}
</code></pre>
]]></content:encoded>
			<wfw:commentRss>http://chikuyonok.ru/2013/02/docpad/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>CodeMirror Movie</title>
		<link>http://chikuyonok.ru/2013/02/codemirror-movie/</link>
		<comments>http://chikuyonok.ru/2013/02/codemirror-movie/#comments</comments>
		<pubDate>Sun, 24 Feb 2013 20:02:48 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Прочее]]></category>
		<category><![CDATA[codemirror]]></category>
		<category><![CDATA[demo]]></category>
		<category><![CDATA[movie]]></category>

		<guid isPermaLink="false">http://chikuyonok.ru/?p=794</guid>
		<description><![CDATA[Для создания Emmet было создано несколько сторонних проектов, с которыми я вас познакомлю в нескольких постах этого блога. Первое, с чем я хотел бы вас познакомить, это CodeMirror Movie. Его вы можете у видеть на многих страницах сайта с документацией: это тот самый механизм, который показывает интерактивные презентации. Когда я только начинал работу над документацией, [...]]]></description>
				<content:encoded><![CDATA[<p>Для создания <a href="http://emmet.io">Emmet</a> было создано несколько сторонних проектов, с которыми я вас познакомлю в нескольких постах этого блога.</p>
<p>Первое, с чем я хотел бы вас познакомить, это <a href="https://github.com/sergeche/codemirror-movie">CodeMirror Movie</a>. Его вы можете у видеть на многих страницах <a href="http://docs.emmet.io">сайта с документацией</a>: это тот самый механизм, который показывает интерактивные презентации.</p>
<p>Когда я только начинал работу над документацией, мне хотелось более наглядно показать работу действий Emmet. Читать длинные тексты, описывающие особенности работы, всегда скучно и утомительно, гораздо приятнее смотреть как это работает «в живую».</p>
<p>Обычно такие задачи решаются записью видео-роликов, но такой вариант не устраивал меня по многим причинам:</p>
<ul>
<li>Запись <em>качественного</em> ролика требует слишком много времени. Например, на создание <a href="https://vimeo.com/7405114">шестиминутного ролика про Zen Coding v0.5</a> у меня ушло около четырёх часов.</li>
<li>Видео довольно сложно обновлять. Например, если обнаружится ошибка или пользователям будет не понятно, как работает какое-то действие, скорее всего, понадобится ещё несколько часов на перезапись ролика.</li>
<li>Так как сам Emmet написан на чистом JS (а значит работает в браузерах), хотелось, чтобы пользователи не только <em>смотрели</em>, как работает Emmet, но и <em>пробовали его в действии</em> прямо на страницах документации.</li>
</ul>
<p>Для решения этих и многих других проблем и был создан <a href="https://github.com/sergeche/codemirror-movie">CodeMirror Movie</a>. Принцип его работы довольно прост: вы создаёте небольшой <em>сценарий</em>, в котором указываете, что нужно сделать, например, написать текст, немного подождать, а потом показать всплывающую подсказку.</p>
<p>Как можно догадаться из названия, в основе проекта лежит замечательный редактор <a href="http://codemirror.net">CodeMirror</a>, а это значит, что вы можете создавать презентации для любого языка программирования, который поддерживает этот редактор.</p>
<h2>Создание презентации</h2>
<p>Как правило, для создания экземпляра редактора CodeMirror вы создаёте элемент <code>&lt;textarea&gt;</code> с начальным содержимым редактора и вызываете следующий JS-код:</p>
<pre class="brush: js">
var myCodeMirror = CodeMirror.fromTextArea(myTextArea);
</pre>
<p>Создать презентацию так же легко: вы создаёте <code>&lt;textarea&gt;</code> с начальным содержимым редактора и дописываете туда сценарий презентации, разделив эти секции строкой <code>@@@</code>:</p>
<pre class="brush: html">
&lt;textarea id="code"&gt;
&amp;lt;div class="content"&amp;gt;
    |
&amp;lt;/div&amp;gt;
@@@
type: Hello world
wait: 1000
tooltip: Sample tooltip
&lt;/textarea&gt;
</pre>
<p>Для инициализации ролика нужно вызвать метод <code>CodeMirror.movie()</code>, передав первым параметром ID элемента <code>&lt;textarea&gt;</code> или сам элемент:</p>
<pre class="brush: js">
var movie = CodeMirror.movie('code');

// начинаем воспроизведение
movie.play();
</pre>
<h2>Сценарий презентации</h2>
<p>Как было отмечено выше, для создания презентации вам нужно написать её <em>сценарий</em>.</p>
<p>Сценарий представляет собой <em>список команд</em>, которые нужно выполнить. Каждая команда пишется на отдельной строке в виде <code>название: значение</code>. В качестве значения записывается JS-объект с параметрами команды, однако каждая команда имеет довольно неплохие базовые значения, поэтому достаточно передать всего лишь значение самого главного параметра. Например, вот как выглядит сценарий ролика, который должен набрать «Hello world», а через секунду после завершения набора показать всплывающую подсказку с текстом «Movie tooltip»:</p>
<pre>
type: Hello world
wait: 1000
tooltip: Movie tooltip
</pre>
<p>Более подробную информацию о всех командах и примеры использования можно найти на <a href="https://github.com/sergeche/codemirror-movie">странице плагина</a>. Сам плагин вы можете использовать как угодно (лицензия MIT), особенно хорошо он будет смотреться в JS-движках презентаций вроде <a href="http://bartaz.github.com/impress.js/">impress.js</a> или <a href="http://lab.hakim.se/reveal-js/">reveal.js</a>. Надеюсь, вам понравится!</p>
]]></content:encoded>
			<wfw:commentRss>http://chikuyonok.ru/2013/02/codemirror-movie/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Вышел Emmet v1.0</title>
		<link>http://chikuyonok.ru/2013/02/emmet-v1/</link>
		<comments>http://chikuyonok.ru/2013/02/emmet-v1/#comments</comments>
		<pubDate>Sun, 24 Feb 2013 20:01:48 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Верстка]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[emmet]]></category>
		<category><![CDATA[html]]></category>

		<guid isPermaLink="false">http://chikuyonok.ru/?p=791</guid>
		<description><![CDATA[Я рад сообщить, что после более полугода разработки в свет вышел Emmet (бывший Zen Coding) v1.0. Возможно, вы уже используете Emmet в течение нескольких месяцев, но только сейчас, после многочисленных исправлений ошибок и улучшений, я могу сказать, что он работает так, как надо. Что поменялось со времён Zen Coding? Во-первых, поменялось название. Emmet будет брэндом [...]]]></description>
				<content:encoded><![CDATA[<p>Я рад сообщить, что после более полугода разработки в свет вышел <a href="http://emmet.io">Emmet</a> (бывший Zen Coding) v1.0. Возможно, вы уже используете Emmet в течение нескольких месяцев, но только сейчас, после многочисленных исправлений ошибок и улучшений, я могу сказать, что он работает так, как надо.</p>
<p>Что поменялось со времён Zen Coding?</p>
<p>Во-первых, поменялось название. Emmet будет брэндом для новых инструментов и не все они будут связаны с написанием кода (coding).</p>
<p>Во-вторых, у проекта появился <a href="http://emmet.io">полноценный сайт</a> и <a href="http://docs.emmet.io">обширная документация</a> по всем возможностям. Теперь не надо искать в интернете обрывки информации о том, как пользоваться проектом, вся наиболее полная информация собрана в одном месте.</p>
<p>В-третьих, улучшилась работа с CSS: значения свойств можно писать прямо в аббревиатуре. Также был учтён опыт и пожелания пользователей: благодаря модулю <a href="http://docs.emmet.io/css-abbreviations/fuzzy-search/">нечётного поиска</a> вам необязательно запоминать громоздкие названия аббревиатур, достаточно написать всего несколько символов (например, <code>ov:h</code> == <code>ov-h</code> == <code>o-h</code> == <code>oh</code> == <code>overflow: hidden</code>);</p>
<p>Вот список остальных значимых изменений:</p>
<ul>
<li>Полностью переписан код проекта. Он стал более модульным и расширяемым.</li>
<li>Отказ от Python-версии. Мне было довольно сложно поддерживать две версии ядра. Вместо отдельной версии теперь используются мосты на <a href="https://github.com/sergeche/emmet-sublime/tree/master/emmet">Python</a>, <a href="https://github.com/emmetio/emmet-objc">Objective-C</a> и <a href="https://github.com/emmetio/emmet-eclipse">Java</a>, это позволит править баги и добавлять новые возможности очень быстро и сразу на все платформы.</li>
<li>Улучшен <a href="http://docs.emmet.io/abbreviations/implicit-names/">модуль определения неявных имён тэгов</a>. Ранее, если вы пытались развернуть аббревиатуру вроде <code>.item</code>, то в результате могли получить либо <code>&lt;div class="item"&gt;</code>, либо <code>&lt;span class="item"&gt;</code>, в зависимости от типа родительского тэга. Теперь модуль смотрит на название тэга и может вывести, например, <code>&lt;li&gt;</code>, <code>&lt;td&gt;</code>, <code>&lt;option&gt;</code>.</li>
<li><a href="http://docs.emmet.io/customization/">Поддержка расширений</a>. Теперь, чтобы добавить новую аббревиатуру или настроить вывод результата, не надо лезть в код плагина, достаточно создать несколько простых JSON-файлов в специальной папке.</li>
<li><a href="http://docs.emmet.io/abbreviations/lorem-ipsum/">Генератор «Lorem Ipsum»</a>. Ранее, чтобы получить «рыбный» текст для сайта, надо было пользоваться сторонними ресурсами, а затем форматировать результат. Теперь получить такой текст можно прямо в редакторе, причём количество слов в тексте можно регулировать просто дописав число после аббревиатуры. Более того, генератор использует все возможности аббревиатур Emmet, позволяя дописывать нужные атрибуты к генерируемым элементам и регулировать количество создаваемых блоков.</li>
<li><a href="http://docs.emmet.io/abbreviations/syntax/#climb-up-">Новый оператор <code>^</code></a>. Несмотря на то, что в Emmet/Zen Coding довольно давно существует более мощный инструмент в виде <a href="http://docs.emmet.io/abbreviations/syntax/#grouping-">группировки элементов</a>, зачастую осознание того, что следующий элемент в аббревиатуре должен находится уровнем выше, приходит довольно поздно. Пользователям приходилось возвращаться обратно, добавлять скобки и дописывать нужный элемент. Теперь достаточно написать оператор <code>^</code>, чтобы подняться на уровень выше, причём можно использовать несколько операторов подряд.</li>
</ul>
<p>Исходный код и плагины доступны в <a href="https://github.com/emmetio">специальном репозитории</a>. Если вы обнаружили ошибки или у вас есть пожелания по улучшению, буду рад узнать о них в разделе <a href="https://github.com/emmetio/emmet">Issues</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://chikuyonok.ru/2013/02/emmet-v1/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Canvas как способ оптимизации графики</title>
		<link>http://chikuyonok.ru/2011/08/optimize-with-canvas/</link>
		<comments>http://chikuyonok.ru/2011/08/optimize-with-canvas/#comments</comments>
		<pubDate>Wed, 10 Aug 2011 23:02:33 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Работа с графикой]]></category>
		<category><![CDATA[blending]]></category>
		<category><![CDATA[canvas]]></category>
		<category><![CDATA[compositing]]></category>

		<guid isPermaLink="false">http://chikuyonok.ru/?p=763</guid>
		<description><![CDATA[Мы постепенно начинаем обновлять дизайн сайта Аймобилко и уже выкатили пару новых макетов. Самое заметное изменение — это главная страница сайта: Дизайнер очень хорошо постарался: страница выглядит очень красиво и современно. Осталось только перенести всё эту красоту из фотошопа в веб. Центральный элемент страницы — сцена, на которой показываются новинки нашего каталога. На фоне сцены [...]]]></description>
				<content:encoded><![CDATA[<p>Мы постепенно начинаем обновлять дизайн сайта <a href="http://www.imobilco.ru/">Аймобилко</a> и уже выкатили пару новых макетов. Самое заметное изменение — это главная страница сайта:</p>
<p><img src="http://chikuyonok.ru/u/2011/08/ss01.jpg" alt="ss01" title="ss01" width="500" height="428" class="alignnone size-full wp-image-764" /></p>
<p>Дизайнер очень хорошо постарался: страница выглядит очень красиво и современно. Осталось только перенести всё эту красоту из фотошопа в веб.</p>
<p>Центральный элемент страницы — сцена, на которой показываются новинки нашего каталога. На фоне сцены находится очень большая картинка с кулисами. Если присмотреться, то эти кулисы покрыты лёгким шумом, что придаёт им особый колорит:</p>
<p><img src="http://chikuyonok.ru/u/2011/08/example.png" alt="example" title="example" width="486" height="328" class="alignnone size-full wp-image-775" /></p>
<p>Однако вся эта красота на проверку оказалась очень тяжёлой: в одной картинке объединилось всё худшее, что плохо влияет на сжатие. Это и красный цвет (даёт очень сильные артефакты сжатия в JPEG), и мелкий шум (сильные артефакты в JPEG; плохо упаковывается в PNG). Приемлемое качество картинки было достигнуто при размере в <em>330 КБ</em>, что, на мой взгляд, довольно много для одной картинки. Очень хочется, чтобы главная страница загружалась как можно быстрее. Поэтому я решился на один эксперимент.</p>
<h2>Изучаем картинку</h2>
<p>При детальном изучении картинки можно заменить, что состоит из двух слоёв: собственно, сама сцена с кулисами и слой с шумом. Но шум — это <a href="http://ru.wikipedia.org/wiki/Процедурное_текстурирование">процедурная текстура</a>, которую можно легко сгенерировать прямо в браузере пользователя с помощью canvas. Тогда я смогу отдавать пользователю только базовую картинку, которая очень хорошо сжимается за счёт плавных цветовых переходов.</p>
<p>Простой алгоритм монохромного шума выглядит так:</p>
<pre class="brush: js">
var canvas = document.createElement('canvas');
canvas.width = canvas.height = 200;
var ctx = canvas.getContext('2d');

// получаем все пиксели изображения
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var pixels = imageData.data;

for (var i = 0, il = pixels.length; i &lt; il; i += 4) {
	var color = Math.round(Math.random() * 255);
	// так как шум монохромный, в каналы R, G и B кладём одно и то же значение
	pixels[i] = pixels[i + 1] = pixels[i + 2] = color;

	// делаем пиксель непрозрачным
	pixels[i + 3] = 255;
}

// записываем пиксели обратно на холст
ctx.putImageData(imageData, 0, 0);

document.body.appendChild(canvas);
</pre>
<p>Получим вот такой результат:</p>
<p><img src="http://chikuyonok.ru/u/2011/08/noise.png" alt="noise" title="noise" width="200" height="200" class="alignnone size-full wp-image-765" /></p>
<p>Вполне неплохо для начала. Однако нам не достаточно просто сгенерировать слой с шумной текстурой и наложить его на кулисы с полупрозрачностью. Если ещё внимательней присмотреться к картинке, то можно заменить, что там нет светлых пикселей, есть только тёмные. То есть <em>монохромный шум должен быть наложен на картинку в режиме Multiply</em>.</p>
<h2>Режимы наложения</h2>
<p>Каждый, кто работал с фотошопом и другими продвинутыми графическими редакторами, знает, что такое режимы наложения слоёв:</p>
<p><img src="http://chikuyonok.ru/u/2011/08/blending-modes.png" alt="blending-modes" title="blending-modes" width="210" height="47" class="alignnone size-full wp-image-766" /></p>
<p>Кому-то они могут показаться запредельно сложными с точки зрения программной реализации, однако на самом деле практически все режимы наложения основаны на очень простых алгоритмах. Например, алгоритм режима наложения Multiply выглядит так:</p>
<pre>(colorA * colorB) / 255</pre>
<p>То есть просто умножаем два цвета и делим результат на 255 (отсюда и название Multiply: «умножение»).</p>
<p>Доработаем нашу функцию: загрузим картинку, сгенерируем шум и наложим его в режиме Multiply:</p>
<pre class="brush: js">
// Загружаем картинку. Обязательно ждём, пока она полностью загрузится
var img = new Image;
img.onload = function() {
	addNoise(img);
};
img.src = "stage-bg.jpg";


function addNoise(img) {
	var canvas = document.createElement('canvas');
	canvas.width = img.width;
	canvas.height = img.height;

	var ctx = canvas.getContext('2d');

	// нарисуем картинку на холсте, чтобы получить её пиксели
	ctx.drawImage(img, 0, 0);

	// получаем все пиксели изображения
	var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
	var pixels = imageData.data;

	for (var i = 0, il = pixels.length; i &lt; il; i += 4) {
		// генерируем пиксель «шума»
		var color = Math.random() * 255;

		// накладываем пиксель шума в режиме multiply на каждый канал
		pixels[i] = pixels[i] * color / 255;
		pixels[i + 1] = pixels[i + 1] * color / 255;
		pixels[i + 2] = pixels[i + 2] * color / 255;
	}

	ctx.putImageData(imageData, 0, 0);
	document.body.appendChild(canvas);
}
</pre>
<p>Получим что-то типа этого:</p>
<p><img src="http://chikuyonok.ru/u/2011/08/stage-noise.png" alt="stage-noise" title="stage-noise" width="323" height="224" class="alignnone size-full wp-image-767" /></p>
<p>Уже похоже на правду, но шум получился грубый: нужно <em>наложить его с меньшей прозрачностью</em>.</p>
<h2>Альфа-композиция</h2>
<p>Процесс смешивание двух цветов с учётом прозрачности называется «<a href="http://en.wikipedia.org/wiki/Alpha_compositing">альфа-композиция</a>». В простейшем варианте алгоритм смешивания выглядит так:</p>
<pre>colorA * alpha + colorB * (1 - alpha)</pre>
<p>где <code>alpha</code> — это коэффициент смешивания (прозрачность) от 0 до 1. В данном случае важно правильно выбрать, что будет фоновым изображением (<code>colorB</code>), а что будет накладываемым (<code>colorA</code>). В нашем случае фоновой будет сцена, а шум — накладываемым.</p>
<p>Добавим в функцию <code>addColor()</code> дополнительный параметр <code>alpha</code> и модифицируем сам алгоритм с учётом альфа-композиции:</p>
<pre class="brush: js">
var img = new Image;
img.onload = function() {
	addNoise(img, 0.4);
};
img.src = "stage-bg.jpg";


function addNoise(img, alpha) {
	var canvas = document.createElement('canvas');
	canvas.width = img.width;
	canvas.height = img.height;

	var ctx = canvas.getContext('2d');
	ctx.drawImage(img, 0, 0);

	var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
	var pixels = imageData.data, r, g, b;

	for (var i = 0, il = pixels.length; i &lt; il; i += 4) {
		// генерируем пиксель «шума»
		var color = Math.random() * 255;

		// высчитываем итоговый цвет в режиме multiply без альфа-композиции
		r = pixels[i] * color / 255;
		g = pixels[i + 1] * color / 255;
		b = pixels[i + 2] * color / 255;

		// альфа-композиция
		pixels[i] =     r * alpha + pixels[i] * (1 - alpha);
		pixels[i + 1] = g * alpha + pixels[i + 1] * (1 - alpha);
		pixels[i + 2] = b * alpha + pixels[i + 2] * (1 - alpha);
	}

	ctx.putImageData(imageData, 0, 0);
	document.body.appendChild(canvas);
}
</pre>
<p>Получаем именно то, что нам нужно: слой шума, наложенный на картинку в режиме Multiply и прозрачностью 20%:</p>
<p><img src="http://chikuyonok.ru/u/2011/08/stage-noise-alpha.png" alt="stage-noise-alpha" title="stage-noise-alpha" width="323" height="224" class="alignnone size-full wp-image-768" /></p>
<h2>Оптимизация</h2>
<p>У меня картинка генерируется примерно за 400 мс, что довольно заметно. Поэтому мы оптимизируем код, чтобы он работал быстрее.</p>
<p>Размер моей картинки 1293×897 пикселей, что в итоге даёт 1 159 821 итераций цикла. Это довольно много, поэтому в первую очередь нужно оптимизировать операции вычисления, а именно убрать ненужные и повторяющиеся операции.</p>
<p>Например, в цикле три раза высчитывается значение <code>1 - alpha</code>, хотя это постоянное значение для всей функции, поэтому делаем новую переменную за пределами цикла:</p>
<pre class="brush: js">var alpha1 = 1 - alpha;</pre>
<p>Далее, при генерации пикселя шума используется формула <code>Math.random() * 255</code>, однако дальше мы делим этот цвет на 255: <code>r = pixels[i] * color / 255</code>. Соответственно, умножение и деление на 255 можно смело убирать.</p>
<p>Эти простые операции снизили время выполнения скрипта с 400 мс до 300 мс (-25%).</p>
<p>Помним, что у нас больше миллиона итераций по массиву, поэтому каждая мелочь на счету. В цикле мы два раза обращаемся к массиву <code>pixels</code>, сначала чтобы получить значения для вычисления multiply-пикселя, а затем чтобы сделать альфа-композицию. Доступ к массиву довольно дорогая операция, поэтому сохраняем оригинальное значение пикселя в переменную (то есть оставляем только одно обращение к массиву):</p>
<pre class="brush: js">
var origR = pixels[i],
	origG = pixels[i + 1],
	origB = pixels[i + 2];
</pre>
<p>Это экономит ещё около 40 мс.</p>
<p>С учётом всех оптимизаций функция <code>addNoise()</code> выглядит вот так:</p>
<pre class="brush: js">
function addNoise(img, alpha) {
	var canvas = document.createElement('canvas');
	canvas.width = img.width;
	canvas.height = img.height;

	var ctx = canvas.getContext('2d');
	ctx.drawImage(img, 0, 0);

	var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
	var pixels = imageData.data, r, g, b, origR, origG, origB;
	var alpha1 = 1 - alpha

	for (var i = 0, il = pixels.length; i &lt; il; i += 4) {
		// генерируем пиксель «шума»
		var color = Math.random();

		origR = pixels[i];
		origG = pixels[i + 1];
		origB = pixels[i + 2];

		// высчитываем итоговый цвет в режиме multiply без альфа-композиции
		r = origR * color;
		g = origG * color;
		b = origB * color;

		// альфа-композиция
		pixels[i] =     r * alpha + origR * alpha1;
		pixels[i + 1] = g * alpha + origG * alpha1;
		pixels[i + 2] = b * alpha + origB * alpha1;
	}

	ctx.putImageData(imageData, 0, 0);
	document.body.appendChild(canvas);
}
</pre>
<p>Скорость выполнения скрипта — около 170 мс (было 400 мс), что довольно неплохо.</p>
<h2>Ещё больше оптимизаций</h2>
<p>Внимательный читатель мог заметить, что картинка с кулисами — красная. То есть информация об изображении присутствует только в красном канале, в синем и зелёном её нет. Если её нет, зачем делать вычисления для этих каналов? Поэтому оставляем расчёты только для красного канала: <em>конкретно в моём случае</em> это даст аналогичный результат, а время выполнения снизит до 80 мс:</p>
<pre class="brush:js">
for (var i = 0, il = pixels.length; i &lt; il; i += 4) {
	origR = pixels[i];
	pixels[i] = origR * Math.random() * alpha + origR * alpha1;
}
</pre>
<p><strong>Добавлено:</strong> читатель <a href="https://twitter.com/#!/sergeyev">@Sergeyev</a> указал, что можно ещё сократить время выполнения скрипта (-20%), убрав ненужные операции:</p>
<pre class="brush:js">
for (var i = 0, il = pixels.length; i &lt; il; i += 4) {
	pixels[i] = pixels[i] * (Math.random() * alpha + alpha1);
}
</pre>
<h2>Результат</h2>
<p>Результат получился довольно неплохим:</p>
<ul>
<li>Вес изображения снизился с 330 КБ до 70 КБ + 1 КБ пожатого JS-кода. На самом деле, картинку можно было бы ещё больше ужать, потому что слой с шумом скроет большинство артефактов JPEG-сжатия.</li>
<li>Такая оптимизация соответствует практикам progressive enhancement: пользователи с браузерами, в которых нет canvas (например, IE6) или отключён JS всё равно получат картинку, но менее детализированную.</li>
</ul>
<p>Единственный минус, который я вижу — это выполнение наложения каждый раз при загрузке страницы, в то время как обычная картинка может быть просто закэширована браузером. Но, во-первых, время выполнения наложения довольно низкое (80 мс), а во-вторых, как вариант, результат можно хранить в localStorage в виде data:url и при следующей загрузке страницы доставать из кэша. Но моя картинка занимает более 1 МБ, так что я не стал сохранять её — доступное пространство можно и нужно использовать с большей пользой.</p>
<p>В целом, считаю эксперимент удавшимся, посмотрим, будут ли жалобы со стороны пользователей. Кто хочет попробовать что-то подобное на своих проектах — добро пожаловать в <a href="http://media.chikuyonok.ru/canvas-blending/">онлайн-демо</a>, в котором я собрал реализации популярных режимов наложения, чтобы оценить их скорость и качество.</p>
<p><strong>Добавлено:</strong> некоторые читатели и <em>Денис</em> в частности справедливо заметил в комментариях, что практически идентичного результата можно добиться <a href="http://float-left.ru/im/">наложением слоя с шумом поверх картинки</a>. Тут скорее речь идёт о не совсем удачном примере, выбранном для статьи, нежели о неправильности подхода в целом.</p>
]]></content:encoded>
			<wfw:commentRss>http://chikuyonok.ru/2011/08/optimize-with-canvas/feed/</wfw:commentRss>
		<slash:comments>59</slash:comments>
		</item>
		<item>
		<title>Веб-разработка в Eclipse: JavaScript</title>
		<link>http://chikuyonok.ru/2011/07/eclipse-webdev2/</link>
		<comments>http://chikuyonok.ru/2011/07/eclipse-webdev2/#comments</comments>
		<pubDate>Tue, 19 Jul 2011 22:03:16 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Прочее]]></category>
		<category><![CDATA[code complete]]></category>
		<category><![CDATA[eclipse]]></category>
		<category><![CDATA[jsdt]]></category>
		<category><![CDATA[web tools]]></category>

		<guid isPermaLink="false">http://chikuyonok.ru/?p=754</guid>
		<description><![CDATA[Как отмечалось ранее, для работы JS вместо Spket IDE я теперь использую Eclipse JSDT, который входит в состав Eclipse Web Tools Project. Причины для такого перехода вполне естественные: проекты, с которыми я работаю, становятся всё сложнее и больше, нужно больше удобства и контроля над ситуацией. В JSDT меня больше всего привлекло следующее: Рефакторинг: переименование объектов, [...]]]></description>
				<content:encoded><![CDATA[<p>Как отмечалось <a href="/2011/07/eclipse-webdev1/">ранее</a>, для работы JS вместо <a href="http://www.spket.com/">Spket IDE</a> я теперь использую <a href="http://wiki.eclipse.org/JSDT">Eclipse JSDT</a>, который входит в состав<a href="http://www.eclipse.org/webtools/"> Eclipse Web Tools Project</a>. Причины для такого перехода вполне естественные: проекты, с которыми я работаю, становятся всё сложнее и больше, нужно больше удобства и контроля над ситуацией.</p>
<p>В JSDT меня больше всего привлекло следующее:</p>
<ul>
<li>Рефакторинг: переименование объектов, выделение блока в отдельную переменную, объединение определения переменной и присваивания и т.д. Некоторые вещи вроде выделения в метод пока толком не работают, но, надеюсь, в ближайшее время это будет исправлено.</li>
<li>Валидация кода. Помимо обычной проверки синтаксиса, можно настроить более сложные проверки вроде поиска неиспользованных переменных, недостижимый код, переопределение переменной из внешней области видимости и т.д.</li>
<li>Выделение фрагментов camelCase-переменных с помощью Shift+Alt+← и Shift+Alt+→</li>
<li>Поддержка JSDoc.</li>
<li>Удобный Outline/Quick outline; дополнительные окна, в которых показывается документация и код определения текущего объекта.</li>
<li>Автоматическая отбивка кода при его перемещении из/в блок.</li>
<li>Подключение внешних библиотек.</li>
<li>Встроенный дебаггер.</li>
</ul>
<p>Лучше всего будет, если читатель поставит <a href="http://www.eclipse.org/downloads/packages/eclipse-ide-javascript-web-developers/indigor">Eclipse for JavaScript Web Developers</a> и изучит все настройки и менюшки — в том числе контекстные — JSDT (лучше включить перспективу JavaScript), потому что возможностей действительно очень много и их сложно описать в одной статье.</p>
<p>Однако при всей «крутости» этой среды разработки, в ней есть ряд проблем, с которыми пришлось столкнуться прежде, чем окончательно перейти на JSDT.</p>
<h2>Начинаем работу</h2>
<p>Для того, чтобы полноценно использовать все возможности JSDT, обязательно нужно создать проект с JavaScript-природой. Делается очень легко: вызываем File &gt; New &gt; Project&#8230; и в появившемся окошке выбираем JavaScript Project. В появившемся диалоговом окне вводим название проекта и жмём Finish. По умолчанию создаётся веб-проект с поддержкой DOM, папкой с JS-исходниками является сам проект. Когда вы лучше освоитесь с JSDT, то сможете более тонко настраивать проект: указывать подключаемые библиотеки, исключать ненужные файлы и папки из индекса. Пока оставим как есть.</p>
<h2>Module pattern</h2>
<p>Главным преимуществом для меня в Spket IDE была поддержка современных паттернов, в том числе и модуля:</p>
<pre class="brush: js">
var module = (function() {
	return {
		method: function() {}
	};
})();
</pre>
<p>В Spket такая конструкция без проблем отображается в outline и по ней работает code complete, но в JSDT ни то, ни другое не работает:</p>
<p><img src="http://chikuyonok.ru/u/2011/07/ss011.png" alt="ss011" title="ss011" width="751" height="407" class="alignnone size-full wp-image-755" /></p>
<p>Небольшой JSDoc  исправит ситуацию:</p>
<pre class="brush: js">
/**
 * @type module
 */
var module = (function() {
	return {
		/**
		 * @memberOf module
		 */
		method: function() {}
	};
})();
</pre>
<p>Теперь работает как надо:</p>
<p><img src="http://chikuyonok.ru/u/2011/07/ss021.png" alt="ss021" title="ss021" width="751" height="407" class="alignnone size-full wp-image-756" /></p>
<p>Но есть ещё одна проблема: приватные переменные и методы модуля не отображаются в Outline, а очень хотелось бы. Это можно исправить, описав самовызывающуюся функцию как конструктор несуществующего класса:</p>
<pre class="brush: js">
/**
 * @memberOf __module
 * @type module
 */
var module = (/** @constructor */ function() {

	function myPrivateMethod() {

	}

	return {
		/**
		 * @memberOf module
		 */
		method: function() {}
	};
})();
</pre>
<p><img src="http://chikuyonok.ru/u/2011/07/ss031.png" alt="ss031" title="ss031" width="751" height="407" class="alignnone size-full wp-image-757" /></p>
<p>Как видно из примера, я описал несуществующий класс <code>__module</code>, двойное подчёркивание я использую в качестве своеобразного соглашения об именовании объектов. Проблема в том, что этот несуществующий класс попадёт в code complete всего проекта, и использование двойного подчёркивания — простой и понятный способ отфильтровать ненужные данные при вызове code complete. Однако этот недостаток очень легко можно превратить в достоинство: таким образом можно описывать структуры объектов, доступ к описанию которых затруднён из-за отсутствия строгой типизации в JS:</p>
<p><img src="http://chikuyonok.ru/u/2011/07/ss041.png" alt="ss041" title="ss041" width="775" height="476" class="alignnone size-full wp-image-758" /></p>
<h2>Поддержка популярных библиотек</h2>
<p>Базовый набор библиотек для JSDT довольно невелик: это стандартные объекты JavaScript (<code>Array</code>, <code>String</code> и прочее) и стандартный DOM (<code>HTMLElement</code>, <code>document</code> и так далее). То есть если мы напишем, например, <code>document.</code> и вызовем code complete, то увидем список свойств и методов объекта <code>document</code>, экземпляра класса <code>Document</code>. Но современная веб-разработка немыслима без использования популярных библиотек вроде jQuery.</p>
<p>Имея в своём распоряжении JSDoc, можно создать описание практически любого популярно фреймворка и использовать его для подсказок в коде. Так как таких описаний в интернете найдено не было, я воспользовался своим любимым принципом «если хочешь, чтобы что-то было сделано хорошо, сделай это сам» и запустил проект, в котором создаю описания популярных библиотек и фреймворков, с которыми работаю:</p>
<h3><a href="https://github.com/sergeche/jsdt-docs">jsdt-docs</a> — JSDoc для популярных библиотек</h3>
<p>В этом проекте сейчас есть следующие библиотеки:</p>
<ul>
<li><a href="http://www.modernizr.com/">Modernizr 2</a></li>
<li>Browser Addons — разные методы и свойства, которые почему-то отсутствуют в стандартном описании DOM в JSDT.</li>
<li>console — небольшое описание объекта <code>console</code>, который присутствует в современных браузерах.</li>
<li>CSS2Properties — список CSS-свойств для свойства <code>style</code> DOM-элементов. Несмотря на то, что он называется CSS2, в нём присутствуют и CSS3-свойства: такое название выбрано потому, что в стандартном описании <code>Element.prototype.style</code> является объектом класса <code>CSS2Properties</code>.</li>
<li><a href="https://github.com/millermedeiros/js-signals">JS SIgnals</a></li>
<li><a href="http://nodejs.org">Node.JS</a></li>
<li><a href="http://socket.io/">Socket.IO</a></li>
<li><a href="http://documentcloud.github.com/underscore/">Underscore.js</a>.</li>
<li><a href="http://zeptojs.com/">Zepto.js</a></li>
</ul>
<p>Добавить библиотеку довольно просто:</p>
<ol>
<li>Идём в настройки: Preferences &gt; JavaScript &gt; Include Path &gt; User Libraries.</li>
<li>Создаём новую библиотеку: нажимаем кнопку New&#8230;</li>
<li>Вводим название библиотеки в появившемся окне и жмём ОК.</li>
<li>Выделив только что добавленную библиотеку, нажимаем на кнопку Add .js file&#8230; и выбираем один или несколько файлов, относящихся к данной библиотеке.</li>
</ol>
<p>После того, как библиотека была создана, нужно добавить её в проект:</p>
<ol>
<li>Идём в настройки проекта: Project &gt; Properties &gt; JavaScript &gt; Include Path &gt; Libraries.</li>
<li>Жмём кнопку Add JavaScript Library, выбираем User Library, а затем и библиотеки, которые хотим добавить.</li>
</ol>
<p>Теперь у вас в проекте будет работать code complete для указанных библиотек:</p>
<p><img src="http://chikuyonok.ru/u/2011/07/ss051.png" alt="ss051" title="ss051" width="775" height="476" class="alignnone size-full wp-image-759" /></p>
<h3>Поддержка jQuery</h3>
<p>Добавление поддержки своего любимого фреймворка оказалась не такой уж и простой задачей. Во-первых, он довольно большой и содержит внушительных объемов документацию. Во-вторых — многие методы в нём «перегружены»: например, <code>val()</code> возвращает текстовое значение поля, а <code>val(str)</code> — записывает его и возвращает уже jQuery-объект.</p>
<p>К счастью, у документации к jQuery есть публичный API, который выдаёт описание всех методов в XML. Я написал <a href="https://github.com/sergeche/jsdt-docs/tree/master/jquery-parser">парсер</a> на node.js, который опрашивает API и преобразует XML в JSDoc, понятный для JSDT (и, надеюсь, другим IDE). Так что в случае выхода новой версии jQuery можно быстро перегенерировать всё документацию. Парсер я писал для себя и по-простому, он не поддерживает передачу параметров через командную строку, всё настраивается в <a href="https://github.com/sergeche/jsdt-docs/blob/master/jquery-parser/main.js">main.js</a>:</p>
<ul>
<li>Можно сгенерировать документацию для определённой версии jQuery (<code>TARGET_VERSION</code>).</li>
<li>Можно сгенерировать описание в виде нескольких файлов. Это связано с особенностью JSDT. Как уже отмечалось ранее, в jQuery много «перегруженных» методов, и если их описать в одном JS-файле, то JSDT будет использовать только одно (самое последнее) описание. Однако если сохранить описание методов с одинаковым названием в разных файлах, то JSDT вполне неплохо покажет по ним code complete и документацию. Так что у вас есть выбор: при создании библиотеки jQuery добавить все файлы вида <code>jquery-jsdoc-N.js</code> (Eclipse JSDT) или же только <code>jquery-jsdoс.js</code> если ваша IDE способна прочитать описание с перегруженными методами.</li>
<li>Можно переписать некоторые определения для более удобной работы с code complete (<code>class_map</code>, <code>prefix_map</code>). Например, так переписываются описания для <code>Deferred</code> и <code>Promise</code> объектов, чтобы можно было использовать их описание в JSDoc:</li>
</ul>
<p><img src="http://chikuyonok.ru/u/2011/07/ss06.png" alt="ss06" title="ss06" width="775" height="476" class="alignnone size-full wp-image-760" /></p>
<p>В описании есть ряд классов, которые можно использовать в JSDoc: <code>__jQueryDeferred</code>, <code>__jQueryPromise</code> и <code>__jQueryEvent</code>.</p>
<h3>Пишем плагины к jQuery</h3>
<p>Собственно, как писать плагины рассказывать нет смысла, это <a href="http://docs.jquery.com/Plugins/Authoring">подробно описано в документации</a>. Покажу лишь как сделать так, чтобы ваш плагин появился в code complete. А сделать это очень просто, достаточно методу плагина указать, что он является членом класса jQuery:</p>
<pre class="brush: js">
jQuery.extend(jQuery.fn, {
	/** @memberOf jQuery */
	myPlugin: function() {

	}
});

$('div').m // тут можно вызвать code complete
</pre>
<h3>Поддержка Node.JS</h3>
<p>Отдельно стоит упомянуть Node.JS, так как к модулям нужно обращаться не напрямую, а через функцию <code>require()</code>:</p>
<pre class="brush: js">
var http = require('http');
http.createServer(function() {

});
</pre>
<p>То есть подсказки для модуля должны зависеть от того, какой аргумент передали в функцию <code>require()</code>. В целом, в JSDT эта ситуация решаема: нужно написать отдельный плагин в виде подключаемой библиотеки, который будет находить вызовы функции <code>require()</code>, смотреть на аргумент и возвращать виртуальный объект с необходимыми методами, и может быть я когда-нибудь напишу такую библиотеку. А пока будем довольствоваться малым: указывать тип модуля через <code>JSDoc</code>.</p>
<p>Все модули в <a href="https://github.com/sergeche/jsdt-docs/blob/master/node.js">моей документации</a> описаны в виде классов <code>Node{Name}Module</code>, например: <code>http</code> → <code>NodeHttpModule</code>, <code>util</code> → <code>NodeUtilModule</code>. Поэтому для переменной, в которую записывается модуль, указываем через JSDoc нужный тип:</p>
<pre class="brush: js">
/** @type NodeHttpModule */
var http = require('http');
http. // вызываем code complete
</pre>
<h2>Советы</h2>
<p>В качестве заключения дам несколько советов, которые помогут вам в работе с JSDT:</p>
<ul>
<li>Не используйте фигурные скобки при указании типа переменной с помощью тэга <code>@type</code> — в некоторых случаях JSDT не сможет подцепить тип переменной. Лучше писать так: <code>@type Array</code> (вместо <code>@type {Array}</code>).</li>
<li>При описании модуля тэг <code>@memberOf moduleName</code> достаточно указать только у одного свойства/метода в литерале, все остальные подцепятся автоматически.</li>
<li>Если у вас в проекте есть непосредственно код библиотек (например, jQuery), то его лучше исключать из индекса, чтобы он не мешал работе code complete. Делается это так: заходите в настройки проекта Project &gt; Properties &gt; JavaScript &gt; Include Path &gt; Source. Разворачиваете папку с исходниками, двойной клик по фильтру Excluded и в диалоговом окне в секцию Exclusion patterns добавляете файлы, которые надо исключить.</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://chikuyonok.ru/2011/07/eclipse-webdev2/feed/</wfw:commentRss>
		<slash:comments>35</slash:comments>
		</item>
		<item>
		<title>Веб-разработка в Eclipse: HTML и CSS</title>
		<link>http://chikuyonok.ru/2011/07/eclipse-webdev1/</link>
		<comments>http://chikuyonok.ru/2011/07/eclipse-webdev1/#comments</comments>
		<pubDate>Wed, 13 Jul 2011 21:34:19 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Верстка]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[eclipse]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[wtp]]></category>

		<guid isPermaLink="false">http://chikuyonok.ru/?p=737</guid>
		<description><![CDATA[Когда-то давно, работая ещё в Студии Лебедева, я рассказывал про использование Eclipse IDE в веб-разработке. Тогда моими основными инструментами были Aptana для HTML и CSS и Spket IDE для JS. С тех пор много воды утекло: Aptana всё дальше отходила от идей Eclipse IDE, всё больше превращаясь в самостоятельный продукт для Rails-разработки, а Spket IDE [...]]]></description>
				<content:encoded><![CDATA[<p>Когда-то давно, работая ещё в Студии Лебедева, <a href="http://www.artlebedev.ru/tools/technogrette/soft/eclipse-introduction/">я рассказывал</a> про использование Eclipse IDE в веб-разработке. Тогда моими основными инструментами были <a href="http://www.aptana.com/">Aptana</a> для HTML и CSS и <a href="http://spket.com/">Spket IDE</a> для JS. С тех пор много воды утекло: Aptana всё дальше отходила от идей Eclipse IDE, всё больше превращаясь в самостоятельный продукт для Rails-разработки, а Spket IDE не обновлялся с октября 2009. В это же время проекты, с которыми я работаю, становились всё больше и сложнее и текущего инструментария уже не хватало. Руководствуясь принципом «если хочешь, чтобы что-то было сделано хорошо — сделай это сам» я принялся допиливать IDE до нужного мне состояния.</p>
<p>Самая главная проблема всех редакторов для веб-разработки: они пишутся людьми, которые этой самой веб-разработкой не занимаются. Нет, они [программисты], конечно, могут на досуге собрать в своём редакторе сайтик-другой, но что делать тем, кто занимается веб-разработкой профессионально? Выход я вижу только один: взять хорошую платформу и доработать её до нужного состояния.</p>
<p>Как я уже написал, Aptana превратилось в некого монстра, с избыточным для не-Rails технологам функционалом. Поэтому в качестве платформы я выбрал более простой и, главное, лучше интегрированный в IDE плагин — Eclipse Web Tools Project.</p>
<h2>Eclipse Web Tools Project</h2>
<p>Как можно понять из названия, <a href="http://www.eclipse.org/webtools/">Eclipse WTP</a> — это инструменты для веб-разработки, а не законченный проект (как Aptana, например). Цель этого проекта: создать платформу, на основе которой можно создавать другие, узкоспециализированные инструменты. Этот проект включает в себя редакторы для CSS, HTML, XML, XSL, JavaScript (отдельный большой проект под названием Eclipse JSDT; о нём расскажу в следующих частях). Проект частично включён практически во <a href="http://www.eclipse.org/downloads/">все официальные сборки Eclipse</a>, но для начинающих пользователей рекомендую ставить Eclipse for JavaScript Web Developers, в ней есть всё.</p>
<p>Чтобы не пересказывать <a href="http://help.eclipse.org/indigo/topic/org.eclipse.wst.sse.doc.user/topics/csrcedt004.html">документацию</a>, кратко приведу основные возможности:</p>
<ul>
<li>подсветка кода;</li>
<li>сворачивание/разворачивание кусков кода (code folding);</li>
<li>outline и quick outline (на последний рекомендую обратить особое внимание, по умолчанию вызывается через <span class="pc">Ctrl+O</span><span class="platform-un"> или </span><span class="mac">⌘O</span>);</li>
<li>выделение границ тэгов (aka Balancing), смотрите в основном меню Edit &gt; Expand Selection To;</li>
<li>content assist по тэгам, атрибутам и значениям некоторых атрибутов;</li>
<li>quick fix: переименование тэга (автоматически меняет открывающий и закрывающий тэги), обрамление тэгом;</li>
<li>форматирование всего/выделенного кода;</li>
<li>автоматическое закрытие тэгов.</li>
</ul>
<p>Итак, с платформой определились, теперь нужно её настроить для комфортной работы.</p>
<h2>Включаем поддержку HTML5 и CSS3</h2>
<p>Eclipse WTP в версии 3.3 (которая доступна для Eclipse Indigo) появилась поддержка тэгов HTML5 и некоторых свойств CSS3. Однако сразу они не доступны, нужно немного пошаманить с проектом:</p>
<ol>
<li>Открываем свойства проекта: Project &gt; Properties.</li>
<li>Переходим на вкладку Project Facets и жмём на ссылку Convert to faceted form&#8230;</li>
<li>В появившемся окне выбираем фасет Static Web Module, жмём Apply.</li>
<li>Закрываем окно с настройками проекта.</li>
</ol>
<p>Теперь в content assist будут доступны новые тэги и свойства. Если нет — снова открываем свойства проекта и идём на вкладку Web Content Settings, где настраиваем профили для HTML и CSS.</p>
<h2>Включаем проверку правописания</h2>
<p>Eclipse IDE с версии 3.4 (если мне не изменяет память) поддерживает проверку правописания, и по умолчанию доступен только английский словарь. Чтобы включить проверку правописания русского языка, делаем следующее:</p>
<ol>
<li>Скачиваем и распаковываем <a href="http://media.chikuyonok.ru/eclipse/ru.dict.zip">русский словарь</a>.</li>
<li>Открываем настройки Eclipse, идём в General &gt; Editors &gt; Text Editors &gt; Spelling</li>
<li>В User defined dictionary выбираем распакованный ru.dict.</li>
<li>Ставим кодировку UTF-8</li>
</ol>
<p>Всё, теперь, если редактор, которым вы пользуетесь, написан по гайдам Eclipse Platform, вам будет доступна проверка правописания.</p>
<h2>Eclipse WTP Sugar</h2>
<p>Самую большую ломку при переходе с Aptana на Eclipse WTP у меня вызывало отсутствие content assist для путей к файлам (например, в <code>&lt;script src="..."&gt;</code>): приходилось писать все пути к файлам вручную, что сильно утомляло. Да и в Aptana он был не идеальным: работал только внутри атрибута <code>src</code> (<code>&lt;script src="..."&gt;</code>, <code>&lt;img src="..."&gt;</code> и т.д.), но не работал внутри <code>href</code> (<code>&lt;link href="..."&gt;</code>, <code>&lt;a href="..."&gt;</code>) и уж тем более не в CSS (<code>background url(...)</code>, <code>@import url(...)</code>). </p>
<p>Поэтому, потратив несколько месяцев на изучение Java и архитектуры Eclipse IDE, я написал свой content assist, который исправит этот недостаток, а заодно несколько других (о них чуть позже).</p>
<p>Свой проект я назвал Eclipse WTP Sugar, репозиторий для установки: <a href="http://media.chikuyonok.ru/eclipse/webdev/updates/">http://media.chikuyonok.ru/eclipse/webdev/updates/</a>, исходный код <a href="https://github.com/sergeche/wtp-sugar">доступен на GitHub</a>.</p>
<p>Плагин устанавливается очень просто, как и все остальные:</p>
<ol>
<li>Идём в Help &gt; Install New Software&#8230;</li>
<li>В качестве репозитория (поле Work with) вбиваем <a href="http://media.chikuyonok.ru/eclipse/webdev/updates/">http://media.chikuyonok.ru/eclipse/webdev/updates/</a>.</li>
<li>В появившемся списке плагинов выбираем необходимые и жмём кнопку Next.</li>
<li>Далее проходим стандартную процедуру установки (соглашаемся с лицензией, всё время жмём Next или Finish) и перезапускаем Eclipse.</li>
</ol>
<p>Теперь, при нажатии Ctrl+пробел внутри атрибутов <code>src</code> и <code>href</code> можно увидеть список файлов:</p>
<p><img src="http://chikuyonok.ru/u/2011/07/ss01.png" alt="ss01" title="ss01" width="413" height="288" class="alignnone size-full wp-image-741" /></p>
<p>Список файлов также выдаётся и в CSS, внутри функции <code>url()</code>:</p>
<p><img src="http://chikuyonok.ru/u/2011/07/ss02.png" alt="ss02" title="ss02" width="456" height="259" class="alignnone size-full wp-image-742" /></p>
<p>Список файлов по возможности фильтруется. Например, если его вызвал внутри тэга <code>&lt;script&gt;</code>, то увидите список файлов с расширением <code>js</code>, если внутри <code>&lt;link rel="stylesheet"&gt;</code> — файлы с расширением <code>css</code>.</p>
<p>Возвращаясь к разговору о том, что создатели программ для вёрстки сами толком сайты не верстают. Что если нужно использовать абсолютные пути для ссылок на файлы? По умолчанию считается, что абсолютный путь нужно резолвить относительно папки с проектом. Но почему никому не пришла в голову мысль, что веб-пространство проекта не всегда совпадает с корневой папкой проекта? В этом плагине проблема решена: в настройках проекта можно указать, относительно какой папки нужно резолвить абсолютные пути (Project &gt; Properties &gt; Document root):</p>
<p><img src="http://chikuyonok.ru/u/2011/07/ss03.png" alt="ss03" title="ss03" width="716" height="334" class="alignnone size-full wp-image-743" /></p>
<h3>Quick outline</h3>
<p>Следующее, что хотелось изменить, так это quick outline: специальное контекстное окошко, которое показывает в компактном виде текущую структуру документа:</p>
<p><img src="http://chikuyonok.ru/u/2011/07/ss04.png" alt="ss04" title="ss04" width="589" height="451" class="alignnone size-full wp-image-744" /></p>
<p>Эту структуру можно фильтровать, чтобы найти нужны элемент (поиск осуществляется с начала строки, можно использовать символы * и ?). Но, как видно из скриншота, в структуре выводятся только названия тэгов, что делает её малопригодной. WTP Sugar исправляет этот недостаток, выводя гораздо больше полезной информации, по которой, в том числе, можно искать:</p>
<p><img src="http://chikuyonok.ru/u/2011/07/ss05.png" alt="ss05" title="ss05" width="586" height="401" class="alignnone size-full wp-image-745" /></p>
<h2>Eclipse XV Browser</h2>
<p>Не так давно я написал просмотрщик XML-файлов под названием <a href="http://chikuyonok.ru/2011/02/xv/">XV Browser</a>. В том посте я показал, что его можно использовать внутри Eclipse, однако не учёл один важный факт: работать это будет только если у вас Mac, а в качестве основного браузера используется Safari с установленным плагином XV <img src='http://chikuyonok.ru/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>Eclipse XV Browser исправляет этот недостаток. Он представляет из себя обёртку для встроенного браузера (это может быть Webkit либо Mozilla, в зависимости от ваших настроек и версии Eclipse). С помощью этого плагина можно прросматривать <em>любой</em> сайт в виде XML-структуры (Window &gt; Show View &gt; Other&#8230; &gt; XV Browser), либо открывать просматривать XML-файлы проекта (правый клик по файлу, Open With&#8230; &gt; XV Browser).</p>
<h2>Пожелания?</h2>
<p>Если есьт какие-то идеи по улучшению плагинов — пишите в <a href="https://github.com/sergeche/wtp-sugar/issues">Issues</a> проекта. Пока в планах:</p>
<ul>
<li>content assist по классам и идентификаторам, описанных в CSS;</li>
<li>быстрый переход к CSS-определению класса или идентификатора;</li>
<li>виджеты для редактирования CSS Gradients и CSS Box Shadow.</li>
</ul>
<p>В следующей статье рассмотрим, как улучшить работу с Eclipse JSDT (JavaScript).</p>
]]></content:encoded>
			<wfw:commentRss>http://chikuyonok.ru/2011/07/eclipse-webdev1/feed/</wfw:commentRss>
		<slash:comments>65</slash:comments>
		</item>
		<item>
		<title>Список блоков с разным вертикальным выравниванием</title>
		<link>http://chikuyonok.ru/2011/04/inline-vertical-align/</link>
		<comments>http://chikuyonok.ru/2011/04/inline-vertical-align/#comments</comments>
		<pubDate>Tue, 05 Apr 2011 22:00:04 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Верстка]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[inline-block]]></category>
		<category><![CDATA[vertical-align]]></category>

		<guid isPermaLink="false">http://chikuyonok.ru/?p=723</guid>
		<description><![CDATA[Когда я работал над очередным улучшением сайта БигБаззи, возникла задача добавления нового режима просмотра предложений — по три штуки в ряд, более компактными блоками (было по два крупных блока в ряд): Дизайнер хочет, чтобы блоки с ценой, статистикой продаж и картинкой были всегда выровнены по одной горизонтальной линии. Но проблема в том, что название предложения [...]]]></description>
				<content:encoded><![CDATA[<p>Когда я работал над очередным улучшением сайта <a href="http://bigbuzzy.ru">БигБаззи</a>, возникла задача добавления нового режима просмотра предложений — по три штуки в ряд, более компактными блоками (было по два крупных блока в ряд):</p>
<p><img src="http://chikuyonok.ru/u/2011/04/example.png" alt="example" title="example" width="800" height="600" class="alignnone size-full wp-image-724" style="max-width:100%;height:auto" /></p>
<p>Дизайнер хочет, чтобы блоки с ценой, статистикой продаж и картинкой были всегда выровнены по одной горизонтальной линии. Но проблема в том, что название предложения всегда произвольной длины и, соответственно, получаются блоки разной высоты.</p>
<p>«Классическое» решение проблемы — прописать минимальную высоту блоку с названием, чтобы угадать некую среднюю величину, выше которой вряд ли что-то появится. Но у этого способа есть два недостатка: во-первых, практика показывает, что обязательно появится название, которое не впишется в отведённое пространство и развалит весь ряд. А во-вторых: при слишком коротких названиях будут появляться неприятные дыры между заголовком и блоком с ценой.</p>
<p>По сути, надо было придумать, <em>как сделать так, чтобы блоки в одном ряду имели разное выравнивание по вертикали</em>:</p>
<p><img src="http://chikuyonok.ru/u/2011/04/scheme.png" alt="scheme" title="scheme" width="500" height="231" class="alignnone size-full wp-image-725" /></p>
<p>Такое расположение усложняется тем, что это не единственный режим просмотра: можно переключится на более крупный, двухколоночный режим. Причём желательно по-меньше трогать DOM-дерево — не делать специальных обёрток вокруг трёх или двух блоков, чтобы потом скриптом их перестраивать. Идеальный вариант — просто менять класс у контейнера и отдать всё изменение структуры на откуп CSS.</p>
<p>Как ни странно, но решение задачи нашлось, причём довольно элегантное.</p>
<p>Обычно такие последовательности блоков делаются самым очевидным образом: через <code>float</code>-элементы. Но этот способ нынче не в моде: все правильные ребята уже давно прочитали статью <a href="http://blog.mozilla.com/webdev/2009/02/20/cross-browser-inline-block/">в блоге Мозиллы</a> о том, как делать их через <code>display: inline-block;</code>.</p>
<p>А что будет, если поставить рядом блоки разной высоты?</p>
<pre class="brush: html">
&lt;style type=&quot;text/css&quot;&gt;
	.wrap, .h, .f {
		display: inline-block;
	}

	.h {
		background: red;
		height: 100px;
		padding: 0 10px;
	}

	.f {
		background: blue;
		height: 20px;
		padding: 0 10px;
		color: #fff;
	}
&lt;/style&gt;

&lt;span class=&quot;wrap&quot;&gt;
	&lt;span class=&quot;h&quot;&gt;header&lt;/span&gt;
	&lt;span class=&quot;f&quot;&gt;footer&lt;/span&gt;
	&lt;span class=&quot;h&quot;&gt;header&lt;/span&gt;
	&lt;span class=&quot;f&quot;&gt;footer&lt;/span&gt;
&lt;/span&gt;
</pre>
<p>Получим вот такой результат:</p>
<p><img src="http://chikuyonok.ru/u/2011/04/ss1.png" alt="ss1" title="ss1" width="250" height="100" class="alignnone size-full wp-image-727" /></p>
<p>По умолчанию у всех блоков <code>vertical-align: baseline</code>, то есть вертикальное выравнивание по базовой линии (в данном случае, если проще — по последней строке текста). Если указать для <code>.h{ vertical-align: top; }</code> и для <code>.f { vertical-align: bottom; }</code>, то получим довольно интересное решение:</p>
<p><img src="http://chikuyonok.ru/u/2011/04/ss2.png" alt="ss2" title="ss2" width="250" height="100" class="alignnone size-full wp-image-728" /></p>
<p>Блоки выровнены именно так, как нужно. Убеждаемся в правильности решения, изменив высоту одного из блоков:</p>
<p><img src="http://chikuyonok.ru/u/2011/04/ss3.png" alt="ss3" title="ss3" width="250" height="200" class="alignnone size-full wp-image-729" /></p>
<p>Дальше всё просто: у шапки и подвала одинаковая и вполне определённая ширина, поэтому прописываем её каждому блоку, а подвал убираем из потока с помощью отрицательного margin’a:</p>
<p><img src="http://chikuyonok.ru/u/2011/04/ss4.png" alt="ss4" title="ss4" width="184" height="200" class="alignnone size-full wp-image-730" /></p>
<p>Чтобы семантически объединить шапку и подвал, можно использовать любой элемент с <code>display: inline</code>. Итоговая версия доступна на <a href="/u/inline-va/">тестовой странице</a>.</p>
<p>Самое интересное, что этот код работает даже в IE6. Помним, что <code>inline-block</code> в этом браузере эмулируется с помощью <code>display: inline</code> и волшебного hasLayout, например, так: <code>display: inline; zoom: 1;</code>.</p>
<p>В этом решении важно помнить следующее:</p>
<ol>
<li>Шапка и подвал в примере <em>пересекаются</em>, поэтому шапке нужно снизу давать отступ и подтягивать подвал любым доступным способом.</li>
<li>Между элементами конструкции не должно быть переводов строк и пробелов, иначе блоки визуально будут разделены.</li>
</ol>
<p>В данном случае мне очень помогло то, что у подвала вполне предсказуемая высота, поэтому у шапки был добавлен снизу большой фиксированный отступ, чтобы блоки не перекрывались. Это решение полностью решило проблему и отображения, и переключения режимов. Можно пойти дальше и подключить CSS3 Media Queries, чтобы обладатели новых браузеров и больших мониторов могли видеть более трёх предложений в ряду, но это уже другая история. Думаю, читатели смогут найти более творческие и интересные применения этого небольшого трюка <img src='http://chikuyonok.ru/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://chikuyonok.ru/2011/04/inline-vertical-align/feed/</wfw:commentRss>
		<slash:comments>42</slash:comments>
		</item>
		<item>
		<title>XV — удобная работа с XML-файлами</title>
		<link>http://chikuyonok.ru/2011/02/xv/</link>
		<comments>http://chikuyonok.ru/2011/02/xv/#comments</comments>
		<pubDate>Wed, 16 Feb 2011 00:38:21 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Прочее]]></category>
		<category><![CDATA[chrome]]></category>
		<category><![CDATA[safari]]></category>
		<category><![CDATA[xml]]></category>
		<category><![CDATA[xpath]]></category>

		<guid isPermaLink="false">http://chikuyonok.ru/?p=711</guid>
		<description><![CDATA[Мне по долгу службы приходится много работать с XML-файлами, которые генерирует CMS. Как правило, для онлайн-просмотра XML-файлов используется Firefox или Internet Explorer, но что делать таким как я, которые используют Safari/Chrome в качестве основного инструмента разработки? Есть плагин xmlview для Safari на Маке от Marc Liyanage, который хоть и спасает ситуацию, но не сильно. Поэтому [...]]]></description>
				<content:encoded><![CDATA[<p>Мне по долгу службы приходится много работать с XML-файлами, которые генерирует CMS. Как правило, для онлайн-просмотра XML-файлов используется Firefox или Internet Explorer, но что делать таким как я, которые используют Safari/Chrome в качестве основного инструмента разработки? Есть плагин <a href="http://www.entropy.ch/software/macosx/xmlviewplugin/">xmlview</a> для Safari на Маке от Marc Liyanage, который хоть и спасает ситуацию, но не сильно.</p>
<p>Поэтому решил написать свой просмотрщик XML-файлов для браузеров на движке Webkit. Встречайте — <a href="https://github.com/sergeche/xmlview">XV</a>:</p>
<p><img src="http://chikuyonok.ru/u/2011/02/overview-2.png" alt="overview-2" title="overview-2" width="984" height="679" class="alignnone size-full wp-image-712" style="max-width:100%;height:auto" /></p>
<h2>Что умеет</h2>
<ul>
<li>Сворачивание/разворачивание элементов. Для избавления от визуального шума соответствующие стрелочки появляются при наведении на элемент. Alt-клик по стрелке сворачивает/разворачивает все внутренние элементы.</li>
<li>Outline для удобного обзора больших документов</li>
<li>Поиск по названию или XPath. По умолчанию ищет вхождение подстроки в названии тэгов и атрибутов, если используются спецсимволы вроде &#8216;/&#8217; или &#8216;[&#8216; то поиск автоматически переключается в XPath-режим.</li>
<li>Режим Quick XPath — моя самая любимая фича. Зажмите клавишу Control (Mac) или Ctrl (PC) при наведении курсора на название элемента или атрибута чтобы войти в этот режим (появится специальный тултип). Нажимайте на клавишу Shift чтобы переключаться между доступными вариантами XPath, а затем просто перетаскивайте текущий элемент в текстовый редактор (используется drag’n’drop). Пользователи Google Chrome могут кликнуть по элементу, чтобы скопировать XPath в буфер обмена. Этот режим очень удобно использовать внутри IDE, например, вот так:</li>
</ul>
<p><img src="http://chikuyonok.ru/u/2011/02/ide.png" alt="ide" title="ide" width="964" height="684" class="alignnone size-full wp-image-713"  style="max-width:100%;height:auto"/></p>
<h3>Скачать</h3>
<ul>
<li><a href="https://chrome.google.com/webstore/detail/eeocglpgjdpaefaedpblffpeebgmgddk">Расширение для Google Chrome</a></li>
<li><a href="https://github.com/sergeche/xmlview/downloads">Плагин для Safari (Mac)</a> — обновлённый xmlview Марка</li>
</ul>
<p>Можно также использовать и в Firefox как стандартный стиль для XML-файла, если скачать соответствующий <a href="https://github.com/downloads/sergeche/xmlview/xv-browser.xsl">XSL-файл</a>:<br />
<code>&lt;?xml-stylesheet type="text/xsl" href="xv-browser.xsl"?&gt;</code>.</p>
<h3>Демонстрация</h3>
<p>Посмотреть, как это выглядит, можно на <a href="http://media.chikuyonok.ru/xmlview/">тестовой страничке</a> (хорошо работает в Safair/Chrome, в Opera и Firefox глючит drag’n’drop).</p>
<p>Исходный код доступен на GitHub:<a href=" http://github.com/sergeche/xmlview/"> http://github.com/sergeche/xmlview/</a><br />
Там же можно сообщения об ошибках (тестировал только на своих XML-файлах) и пожелания.</p>
]]></content:encoded>
			<wfw:commentRss>http://chikuyonok.ru/2011/02/xv/feed/</wfw:commentRss>
		<slash:comments>48</slash:comments>
		</item>
		<item>
		<title>История одной оптимизации</title>
		<link>http://chikuyonok.ru/2010/11/optimization-story/</link>
		<comments>http://chikuyonok.ru/2010/11/optimization-story/#comments</comments>
		<pubDate>Mon, 29 Nov 2010 23:16:30 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Верстка]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[firebug]]></category>
		<category><![CDATA[float]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[repaint]]></category>
		<category><![CDATA[web inspector]]></category>
		<category><![CDATA[оптимизация]]></category>

		<guid isPermaLink="false">http://chikuyonok.ru/?p=687</guid>
		<description><![CDATA[Летом мы запустили новый купонный проект BigBuzzy. Таких проектов к тому времени было довольно много и чтобы выделиться из толпы мы решили немного поменять бизнес-модель: вместо одного предложения в день выдавать четыре. Но, как это обычно бывает, аппетит приходит во время еды, поэтому уже спустя несколько месяцев на главной странице красовалось не 4, а 30 [...]]]></description>
				<content:encoded><![CDATA[<p>Летом мы запустили новый купонный проект <a href="http://www.bigbuzzy.ru">BigBuzzy</a>. Таких проектов к тому времени было довольно много и чтобы выделиться из толпы мы решили немного поменять бизнес-модель: вместо одного предложения в день выдавать четыре. Но, как это обычно бывает, аппетит приходит во время еды, поэтому уже спустя несколько месяцев на главной странице красовалось не 4, а 30 предложений.</p>
<p>И мы сразу же начали получать жалобы о жутких тормозах на главной странице. На поиск и устранение проблем у меня ушло два дня. О том, как находились узкие места и будет сегодняшний рассказ. А заодно научимся пользоваться инструментами вроде Web Inspector&#8217;s Timeline (если вы их ещё не освоили).</p>
<h2>Поиск проблемы</h2>
<p>Итак, мы столкнулись с фактом, что наша главная страница тормозит. Источник проблем был найден сразу: это анимированные таймеры у каждого предложения:</p>
<p><img src="http://chikuyonok.ru/u/2010/11/01-timers.png" alt="01-timers" title="01-timers" width="493" height="68" class="alignnone size-full wp-image-688" /></p>
<p>Самые большие проблемы наблюдались в Firefox: загрузка процессора на главной странице доходила до 70%. Поэтому я начал рассматривать скрипт таймера под микроскопом, а именно в Web Inspector, который по умолчанию входит в состав браузеров Safari и Chrome. Вообще, многие ребята довольно снисходительно относятся к этому инструменту, продолжая по привычке работать в Firebug&#8217;е, а зря. Лично для меня Web Inspector стал основным инструментом для отладки: выглядит он приятнее и содержит ряд полезных нововведений.</p>
<h2>Исследуем узкие места</h2>
<p>Так как сам скрипт таймера довольно простой, то не было смысла заниматься его профилированием — проблема явно где-то в reflow и repaint. Поэтому скрипт нужно исследовать через Timeline:</p>
<p><img src="http://chikuyonok.ru/u/2010/11/02-timeline.png" alt="02-timeline" title="02-timeline" width="659" height="314" class="alignnone size-full wp-image-689" /></p>
<p>Полагаю, что многие читатели ещё ни разу не сталкивались с этим инструментом, поэтому принцип его работы и поиска проблем опишу в небольшом уроке. Стоит отметить, что Web Inspector в Chrome немного круче, чем в Safari, поэтому рекомендую пользоваться первым браузером.</p>
<p>Timeline показывает нам практически все процессы, которые происходят в браузере: запуск скрипта, отработка события, перерисовка экрана, установка таймера, отправка аякс-запроса и т.д. Так как данных довольно много и в них легко запутаться, я рекомендую изолировать исследуемый скрипт — выделить его в отдельную страницу. Для практики можно начать с <a href="/u/performance/">простого шаблона</a>, на котором мы будем экспериментировать.</p>
<p>Открываем шаблон в браузере и запускаем Web Inspector, вкладка Timeline. На странице есть красный квадратик и кнопка «Test». Чтобы начать исследование, нужно нажать на кнопку записи вкладки Timeline <img src="http://chikuyonok.ru/u/2010/11/03-rec.png" alt="03-rec" title="03-rec" width="30" height="22" class="alignnone size-full wp-image-690" />, а потом нажать на кнопку «Test» в основном окне браузера. Наш квадратик посинел и стал больше по высоте, а в Timeline записались следующие события:</p>
<p><img src="http://chikuyonok.ru/u/2010/11/04-test1.png" alt="04-test1" title="04-test1" width="736" height="271" class="alignnone size-full wp-image-691" /></p>
<p>Первые три записи относятся непосредственно к кнопке, которую нажали: применили псевдо-класс <code>:active</code> (Recalculate style), отобразили изменения на экране (Paint), вернули кнопку в исходное состояние, убрав <code>:active</code> (Recalculate style). После того, как пользователь отпустил кнопку мышки сработало событие <code>click</code>, и именно оно и всё, что ниже, нас будет интересовать.</p>
<p>Во время клика сработал следующий скрипт:</p>
<pre class="brush:js">
function test() {
	var el = document.getElementById('test');
	el.style.backgroundColor = 'blue';
	el.style.height = '100px';
}
</pre>
<p>Ничего особенного: просто получили ссылку на элемент и поменяли у него цвет фона и высоту. Этот процесс был отображён на временной шкале: пересчитали стили (Recalculate style), пересчитали геометрию объектов (Layout) и отобразили изменения (Paint).</p>
<p>Как видите, несмотря на то, что мы поменяли два СSS-свойства, пересчёт стилей произошёл всего один раз. Поменяем скрипт:</p>
<pre class="brush:js">
function test() {
	el.style.backgroundColor = 'blue';
	var height = el.offsetHeight;
	el.style.width = '100px';
}
</pre>
<p>Между присваиванием новых стилей мы решили получить высоту объекта. Но временная шкала сильно преобразилась:</p>
<p><img src="http://chikuyonok.ru/u/2010/11/05-test2.png" alt="05-test2" title="05-test2" width="371" height="246" class="alignnone size-full wp-image-692" /></p>
<p>Теперь у нас уже два события Recalculate style, а у самого события <code>click</code> появилась группировка (треугольник слева от жёлтой полоски), которая указывает, какие именно события произошли во время клика.</p>
<p>Этот небольшой пример указывает на две очень важные особенности браузеров — это <em>откладывание перерисовки на момент выхода из функции</em> (первый пример) и <em>существование определённых свойств у элемента, которые принудительно вызывают пересчёт стилей</em> (далее <em>restyle</em>; второй пример). О существовании особых свойств, вызывающих restyle, думаю, многие уже знали: это свойства вроде <code>offsetLeft/Right/Width/Height</code>, <code>clientLeft/Right/Width/Height</code> и так далее. Во втором примере, после установки свойства <code>backgroundColor</code> браузер пометил дерево элементов как требующего пересчёта стилей. А обращение к <code>offsetHeight</code> принудительно вызвало этот  пересчёт. Затем мы установили свойство <code>width</code>, которое отложило пересчёт стилей, геометрии и отображения на момент выхода из потока JS-функций.</p>
<p>Отсюда первое правило: <em>нужно стараться не смешивать получение и запись CSS-свойств</em>. Лучше, например, сначала получить нужные свойства элемента, а затем присвоить новые.</p>
<p>Для любителей jQuery более красноречивым будет вот такой пример:</p>
<pre class="brush:js">
function test() {
	var e = $('#test');
	var width = e.css('width');
	if (width == '50px')
		e.css('width', '100px');

	var height = e.css('height');
	if (height == '50px')
		e.css('height', '100px');
}
</pre>
<p>Вот его шкала:</p>
<p><img src="http://chikuyonok.ru/u/2010/11/06-test3.png" alt="06-test3" title="06-test3" width="303" height="220" class="alignnone size-full wp-image-693" /></p>
<p>Как видите, помимо лишнего Recalculate style появился Layout (reflow), что сделало выполнение скрипта более медленным. Если немного оптимизировать, переместив получение высоты выше в коде:</p>
<pre class="brush:js">
function test() {
	var e = $('#test');
	var width = e.css('width'),
		height = e.css('height');

	if (width == '50px')
		e.css('width', '100px');

	if (height == '50px')
		e.css('height', '100px');
}
</pre>
<p>&#8230;получим совершенно иную картину:</p>
<p><img src="http://chikuyonok.ru/u/2010/11/07-test4.png" alt="07-test4" title="07-test4" width="299" height="190" class="alignnone size-full wp-image-694" /></p>
<p>Лишний Layout (помимо Recalculate style) объясняется тем, что jQuery каждый раз при получении CSS-свойств вызывал <code>window.getComputedStyle()</code>, который принудительно запускает reflow. Справедливости ради стоит отметить, что в функции <code>jQuery.css()</code> есть оптимизация, которая сначала проверяет наличие запрашиваемого свойства в <code>element.style</code> и если его там нет, вызывает <code>window.getComputedStyle()</code>. Но в любом случае, лучше всегда разделять чтение и изменение свойств.</p>
<h2>Таймеры</h2>
<p>Напомню, что я занимался оптимизацией таймеров, которых было несколько. И каждый таймер работает через <em>свой <code>setTimeout()</code></em>. Посмотрим, что это означает на практике:</p>
<pre class="brush:js">
function test() {
	var el = document.getElementById('test');
	setTimeout(function(){
		el.style.backgroundColor = 'blue';
	}, 10);
	setTimeout(function(){
		el.style.width = '100px';
	}, 10);
}
</pre>
<p><img src="http://chikuyonok.ru/u/2010/11/08-test5.png" alt="08-test5" title="08-test5" width="379" height="196" class="alignnone size-full wp-image-695" /></p>
<p>У обоих таймеров одинаковый период ожидания и момент исполнения. На шкале видно, что после каждого таймера был запущен пересчёт стилей. Но в реальности момент исполнения будет далеко не всегда одинаковым. Поэтому поменяем задержку у последнего таймера — поставим 11 мс вместо 10 мс:</p>
<pre class="brush:js">
function test() {
	var el = document.getElementById('test');
	setTimeout(function(){
		el.style.backgroundColor = 'blue';
	}, 10);
	setTimeout(function(){
		el.style.width = '100px';
	}, 11);
}
</pre>
<p>И мы видим, что на шкале появился дополнительный repaint:</p>
<p><img src="http://chikuyonok.ru/u/2010/11/09-test6.png" alt="09-test6" title="09-test6" width="487" height="209" class="alignnone size-full wp-image-696" /></p>
<p>В итоге получалось, что из-за нескольких запущенных <code>setTimeout()</code> срабатывали ненужные перерисовки, которые пользователь всё равно не увидит, но процессор это нагружало прилично. Я переписал код таймеров таким образом, чтобы всё работало через один глобальный таймаут.</p>
<p>Итак, суммируя всё вышесказанное, для оптимизации я</p>
<ul>
<li>разделил чтение и запись CSS-свойств;</li>
<li>дополнительно сделал кэширование текущих значений анимации, чтобы меньше обращаться к элементам;</li>
<li>заменил несколько таймаутов на один.</li>
</ul>
<p>В итоге в Firefox нагрузка на процессор снизилась&#8230; всего на 10%. Вообще, это было крайне странно: даже при наличии всего одного анимированного таймера на странице Firefox грузил процессор на 60%, при том что Webkit грузил всего на 5%. Нужно копать дальше.</p>
<h2>Влияние вёрстки на производительность</h2>
<p>Два года назад я делал большое исследование на тему того, <a href="http://www.artlebedev.ru/tools/technogrette/html/browser-performance/">как вёрстка влияет на производительность браузера</a>. Похоже, у моей проблемы схожие корни, поэтому начал перебирать все известные мне варианты оптимизации. Но, к сожалению, ничего не помогало. </p>
<p>Так как все restyle и reflow процессы я оптимизировал, проблема явно была где-то в repaint. Вспомнил, что в Firefox 3.5 появилось событие <a href="https://developer.mozilla.org/en/Gecko-Specific_DOM_Events#MozAfterPaint">mozAfterRepaint</a>, которое позволяет увидеть области, которые были перерисованы во время repaint. Для удобства было поставлено расширение <a href="https://addons.mozilla.org/en-US/firefox/addon/9620/">Firebug Paint Events</a>, которое позволяет отслеживать перерисовки экрана.</p>
<p>Чтобы описать всю бурю эмоций, которые я испытал после просмотра логов, предлагаю читателю посмотреть на скриншот, где указана область перерисовки во время работы <em>всего одного таймера</em> на странице:</p>
<p><img src="http://chikuyonok.ru/u/2010/11/10-bbz.jpg" alt="10-bbz" title="10-bbz" width="400" height="1000" class="alignnone size-full wp-image-698" /></p>
<p>Я специально оставил только 8 из 30 предложений, чтобы картинка не распирала страницу, но смысл, думаю, ясен: во время анимации даже одного таймера перерисовывалось примерно 90% страницы, 15 раз в секунду. И это при условии, что у цифр таймера указан <code>position:absolute</code>, а у их контейнера <code>overflow:hidden</code>. То есть сама анимация по определению никак не могла повлиять на области вне контейнера (на скриншоте обозначен синим прямоугольником), но перерисовывалась почти вся страница.</p>
<p>Около часа мне понадобилось на то, чтобы найти причину такого странного поведения. Ей оказалось&#8230; свойство <code>float:left</code> у одного из контейнеров. Как только я заменял его на <code>float:none</code> нагрузка на процессор падала ниже 10% (с <code>float:left</code> была около 60%). </p>
<p>Проблема проявляется стабильно, причём не только в Firefox, но и в Opera и IE8. Я сделал простую <a href="/u/performance/fx-demo.html">демку</a>, где можно в живую увидеть эту проблему. В ней всего несколько блоков, однако у них указан <code>box-shadow</code> — очень тяжелое в плане нагрузки на процессор CSS-свойство. В правом верхнем углу есть кнопка, которая всего лишь переключает <code>float</code> у контейнера. Понаблюдайте за нагрузкой на процессор при разных состояниях кнопки, а также за областью перерисовки.</p>
<p>В общем виде проблему можно описать так:</p>
<blockquote><p>Repaint срабатывает на контейнере самого дальнего родителя, у которого указан <code>float:left|right</code>.</p></blockquote>
<p>Схематично это выглядит так:</p>
<p><img src="http://chikuyonok.ru/u/2010/11/11-tree.png" alt="11-tree" title="11-tree" width="595" height="409" class="alignnone size-full wp-image-699" /></p>
<p>Причём проблема не только во <code>float</code>. Я перепробовал различные варианты горизонатльной группировки блоков: <code>display:inline-block</code>, <code>display:table-cell</code>, таблицы и даже новомодные flex box — во всех случаях проблема оставалось. Помогало только абсолютное позиционирование боковых блоков.</p>
<p>В общем виде я проблему решил: поставил боковую панель в коде перед основным контейнером и только ей указал <code>float</code>. Основной контейнер был без <code>float</code> и repaint происходил именно там, где нужно. Однако на живом сайте решить проблему не удалось, так как на большинстве страниц стояли clearfix-элементы, из-за которых макет разваливался. Поэтому пришлось пока отключить анимацию с таймеров <img src='http://chikuyonok.ru/wp-includes/images/smilies/icon_sad.gif' alt=':(' class='wp-smiley' /> </p>
<p style="text-align:center">***</p>
<p>Честно говоря, после таких браузерных крендебобелей на всякие пузомерки типа Peackeeper, которыми так хвастаются разработчики с каждым новым релизом своего браузера, без слёз смотреть не получается. Поэтому мой вам совет: заранее узнавайте о всех интерактивных элементах на странице, не увлекайтесь новым CSS3, продумывайте рост сайта заранее и пользуйтесь правильными инструментами для отладки производительности — тогда будет вам счастье и высокая производительность.</p>
<p>Если хотите узнать больше о профилировании производительности, очень рекомендую <a href="http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/">статью Стояна Стефанова</a> на эту тему.</p>
]]></content:encoded>
			<wfw:commentRss>http://chikuyonok.ru/2010/11/optimization-story/feed/</wfw:commentRss>
		<slash:comments>40</slash:comments>
		</item>
		<item>
		<title>Идея для сниффинга браузеров</title>
		<link>http://chikuyonok.ru/2010/10/browser-sniffing/</link>
		<comments>http://chikuyonok.ru/2010/10/browser-sniffing/#comments</comments>
		<pubDate>Sun, 24 Oct 2010 10:23:10 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Прочее]]></category>
		<category><![CDATA[browser sniffing]]></category>
		<category><![CDATA[chunked encoding]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[идея]]></category>

		<guid isPermaLink="false">http://chikuyonok.ru/?p=680</guid>
		<description><![CDATA[Возникла одна идея, как можно скрестить клиентское и серверное «вынюхивание» браузера. Как это делается сейчас? На сервере мы смотрим на заголовок User Agent и определяем, что это за браузер. Но этот заголовок можно легко подменить (причём необязательно это делать в самом браузере, это может сделать прокси-сервер), поэтому такая проверка совсем ненадёжна. На клиентской стороне у [...]]]></description>
				<content:encoded><![CDATA[<p>Возникла одна идея, как можно скрестить клиентское и серверное «вынюхивание» браузера.</p>
<p>Как это делается сейчас? На сервере мы смотрим на заголовок User Agent и определяем, что это за браузер. Но этот заголовок можно легко подменить (причём необязательно это делать в самом браузере, это может сделать прокси-сервер), поэтому такая проверка совсем ненадёжна. На клиентской стороне у нас гораздо больше возможностей: мы можем не только проверить User Agent браузера, но и его возможности (например, поддержку определённых CSS и JS свойств), что даст нам более точные характеристики браузера.</p>
<p>Собственно, сама идея как раз заключается в том, чтобы скрестить эти два способа: определить с помощью CSS и JS характеристики браузера и в нужный момент сообщить их на сервер. Основа алгоритма — <a href="http://en.wikipedia.org/wiki/Chunked_transfer_encoding">Chunked Transfer Encoding</a>, позволяющий передать ответ (например, HTML-документ) не целиком, а кусками. Итак, сам алгоритм:</p>
<ol>
<li>Когда пользователь запрашивает HTML-страницу, отдаём ему первый фрагмент (chunk), в котором содержится sniffing-код. После этого соединение удерживается на некоторое время, чтобы отдать второй фрагмент документа.</li>
<li>Браузер, получив первый фрагмент, тут же его парсит и пытается отобразить.</li>
<li>В этом первом фрагменте у нас sniffing-код, результатом работы которого является CSS-свойство <code>background-image</code>, применяемое к элементу <code>&lt;html&gt;</code>.</li>
<li>Путь к этой фоновой картинке является уникальным для каждого браузера и так уж получилось, что как только мы применили это свойство элементу, браузер открывает второе соединение и тут же пытается загрузить эту картинку.</li>
<li>Сервер, получив запрос к нашей «картинке» понимает, каким браузером мы зашли и отдаёт вторую часть ответа в первом соединении.</li>
<li>Если обращения к «картинке» не было (например, зашли поисковым роботом), то через какой-нибудь небольшой промежуток времени отдаём стандартный ответ.</li>
<li>После первого «вынюхивания» можно поставить куку, чтобы потом сразу отдавать нужный ответ.</li>
</ol>
<p>В итоге получаем следующие преимущества:</p>
<ul>
<li>Нет никаких редиректов: ответ о типе браузера получаем в одном соединении (полезно для SEO).</li>
<li>Очень сложно обмануть такой сниффер: можно до посинения менять строку User Agent, но результат останется прежним. <s>Гугл сможет эффективней банить Оперу.</s></li>
<li>Более сложные проверки: можно не просто определять название браузера, но и его возможности, благодаря CSS Media Queries.</li>
</ul>
<p>Для желающих поэкспериментировать я написал <a href="https://gist.github.com/31010560af349c448e20">демонстрацию этого подхода</a> на <a href="http://nodejs.org">node.js</a>. К сожалению, у меня на VPS не хватает памяти, чтобы собрать node.js, поэтому предлагаю читателю самому его установить и проверить у себя. <s>Если кто выложит это в онлайн — буду премного благодарен.</s> Выложили: <a href="http://tbms.ru:8126/">http://tbms.ru:8126/</a> (спасибо <a href="http://tbms.ru/">Николаю Митину</a>).</p>
<h2>Полезные уроки</h2>
<p>Пока писал эту демонстрацию, вынес для себя много ценной информации:</p>
<ol>
<li>Первый чанк должен быть достаточно большим, чтобы все браузеры смогли его применить. Самый большой лимит — у движка Webkit: около 2 КБ. Поэтому приходится «добивать» первый фрагмент пробелами, чтобы всё работало как надо.</li>
<li>Webkit очень плохо ведёт себя при переопределении CSS-свойств. Например:
<pre class="brush: css">
html { background-image:url(image1.png); }
html { background-image:url(image2.png); }
		</pre>
<p>		В этом случае Webkit (и десктопный, и мобильный) загрузит обе картинки, хотя понятно, что нужна только последняя. Это ещё одно подтверждение, что мобильные версии сайта лучше делать отдельным проектом, а не вставлять через CSS Media Queries. Иначе попытки сократить траффик заменой больших фоновых картинок на маленькие приведёт к обратному эффекту. Хотя тут нужно больше исследований, возможно, это издержки работы с локальным сервером.
	</li>
<li>Firefox не отработает первый чанк до тех пор, пока не получит элемент <code>&lt;body&gt;</code>. Это довольно серьёзный недостаток в том случае, если вы захотите выдавать уникальные стили и скрипты для браузера в секции <code>&lt;head&gt;</code>. С другой стороны, если послать <code>&lt;script&gt;alert(1)&lt;/script&gt;</code>, то чанк отработает нормально, даже без <code>&lt;body&gt;</code>. Что наталкивает на мысль о существовании неких триггеров, которые заставят браузер сделать то, что нам нужно, осталось только найти наиболее безопасный.</li>
</ol>
<p>Повторюсь, что это только идея, а не призыв к действию. Буду рад, если кто-то на её основе сделает что-то крутое и интересное.</p>
]]></content:encoded>
			<wfw:commentRss>http://chikuyonok.ru/2010/10/browser-sniffing/feed/</wfw:commentRss>
		<slash:comments>38</slash:comments>
		</item>
	</channel>
</rss>

<!-- Dynamic page generated in 0.937 seconds. -->
<!-- Cached page generated by WP-Super-Cache on 2013-06-19 23:45:17 -->
