октябрь 2008

Page 1

октябрь 2008

№2

habradigest #2 • октябрь 2008

i


habradigest #2 • октябрь 2008

ii


ОКТЯБРЬ 2008 №2

Д

.NET →

обрый день, читатель! Перед вами второй выпуск нашего журнала и мы рады представить вам в нем кроме отличных статей ещё и обновленный дизайн.

Добавляем watermark к изображению

2

Exceptions through WCF

4

Кроме того, мы постарались разнообразить наполнение журнала небольшими мелочами, которые вы найдете после всех статей. Это небольшая статистика по тому, кто скачивает и читает наш журнал.

JavaScript Cross Site (XSS) POST

К сожалению, этот выпуск, который был запланирован на середину октября, выходит со значительным опозданием. Мы постараемся, чтобы этого больше не повторилось, и выпустим третий номер в срок, в первых числах ноября.

ВЕБ 2.0 →

Немного обновился и наш сайт http://habradigest.ru. Мы добавили RRS-канал habradigest pulse, который, в соответствии с названием, будет отражать жизненные показатели нашего проекта. В этот раз темой номера стал WordPress, про который написано две статьи, одна из которых, за авторством Aesma, состоит в оригинале аж из 5 частей. Стоит похвалить автора, который за месяц написал столь обширный материал. Автора и его статью в этот раз редакция habradigest и признает лучшими номера. Браво! Окончательно лучший автор октября определится в следующем номере Владимир «XaocCPS» Юнев

JAVASCRIPT →

6

КЛИЕНТСКАЯ ОПТИМИЗАЦИЯ →

Выносим CSS в пост-загрузку

8

Лучшие графические редакторы, написанные на Flash 11 БЛОГ ИМ. DNOVIKOV →

Простые вещи: подгрузка через AJAX HTML-кода, содержащего JavaScript

15

MYSQL →

Оптимизация MySQL запросов

17

MOOTOOLS →

Делаем вращательный регулятор

20

WORDPRESS →

Создаем тему для wordpress

23

Ускоряем wordpress

35

WEB-РАЗРАБОТКА →

CSS Font-Size: em vs. px vs. pt vs. percent

39

ПОСЛЕДНЯЯ СТРАНИЦА →

Статистика

habradigest #2 • октябрь 2008

41

1


.NET

Добавляем watermark к изображению SergeyRodyushkin

Прочитав статью SergeyVoyteshonok (http://sergeyvoyteshonok.habrahabr.ru/blog/41499/), посвященную отрисовке логотипасайта или компании (проще говоря, «водяного знака») на загружаемых пользователями изображениях, я был удивлен некоторой тяжеловесностью предложенного автором решения. Тогда я пообещал немного поэкспериментировать и предложить более рациональный вариант. В моей версии метод отрисовки выглядит так: public void DrawWatermark(Image original, Bitmap watermark, WatermarkPosition position, Color transparentColor, float opacity) { if (original == null) throw new ArgumentNullException(“original”); if (watermark == null) throw new ArgumentNullException(“watermark”); if (opacity < 0 || opacity > 1) throw new ArgumentOutOfRangeException(“Watermark opacity value is out of range”); Rectangle dest = new Rectangle( GetDestination(original.Size, watermark.Size, position), watermark.Size); using (Graphics g = Graphics.FromImage(original)) { ImageAttributes attr = new ImageAttributes(); ColorMatrix matrix = new ColorMatrix(new float[][] { new float[] { opacity, 0f, 0f, 0f, 0f }, new float[] { 0f, opacity, 0f, 0f, 0f }, new float[] { 0f, 0f, opacity, 0f, 0f }, new float[] { 0f, 0f, 0f, opacity, 0f }, new float[] { 0f, 0f, 0f, 0f, opacity } }); attr.SetColorMatrix(matrix); watermark.MakeTransparent(transparentColor); g.DrawImage(watermark, dest, 0, 0, watermark.Width, watermark.Height, GraphicsUnit.Pixel, attr, null, IntPtr.Zero); g.Save(); } }

Дополнительно, мы используем также две вещи: public enum WatermarkPosition { TopLeft = 0, TopRight, BottomLeft, BottomRight, Middle }

для указания точки привязки, и метод, возвращающий нам конкретную точку расположения водяного знака в зависимости от размеров изображений и точки привязки: habradigest #2 • октябрь 2008

private static Point GetDestination(Size originalSize, Size watermarkSize, WatermarkPosition position) { Point destination = new Point(0, 0); switch (position) { case WatermarkPosition.TopRight: destination.X = originalSize.Width - watermarkSize.Width; break; case WatermarkPosition.BottomLeft: destination.Y = originalSize.Height - watermarkSize.Height; break; case WatermarkPosition.BottomRight: destination.X = originalSize.Width - watermarkSize.Width; destination.Y = originalSize.Height - watermarkSize.Height; break; case WatermarkPosition.Middle: destination.X = (originalSize.Width - watermarkSize.Width) / 2; destination.Y = (originalSize.Height - watermarkSize.Height) / 2; break; } return destination; }

На мой взгляд, все достаточно прозрачно. Собственно отрисовка логотипа заняла всего 15 строк, учитывая все навороты форматирования, а без них  — и того меньше! Такого результата позволило достичь применение для решения задачи возможностей .NET-класса ColorMatrix. Вообще, класс ColorMatrix обладает очень широкими возможностями манипуляции настройками изображения. С помощью него можно не только задавать прозрачность, но и изменять контрастность, насыщенность картинки, делать из нее негатив и многое другое. В заключение нужно отметить две вещи. Первая — логотип, используемый в качестве водяного знака, лучше сохранять в PNG. GIF-формат для простых логотипов также допустим. В этом случае мы можем не задавать transparentColor (точнее, вообще убрать строчку watermark.MakeTransparent(transparentColor );) и расширить тип watermark до Image.

2


.NET

Вторая, несколько более важная — мой метод выполняет отрисовку прямо на переданной ему картинке, не создавая нового экземпляра. Поэтому, если вам необходимо сохранять и оригинал, нужно будет немного переделать метод для отрисовки с тем, чтобы он возвращал измененное изображение, не затрагивая исходного. UPD: Если вы загружали исходное изображение из файла и хотите сохранить его под тем же именем, вам также нужно будет создать копию и сохранить ее вне блока using. UPD2: По просьбе Sergeyev добавил демонстрацию результата (слева — исходная картинка, справа — она же с известным всем логотипом. Не фотошоп! :) Ну вот, собственно, и все! P.S.: Вот так, совершенно незаметно, появился мой первый топик на Хабре :)

habradigest #2 • октябрь 2008

3


.NET

Exceptions through WCF RomanNikitin

Давайте поговорим про передачу исключений через WCF сервис. Для начала давате представим ситуацию – у нас есть метод RegisterUser, который соответственно производит регистрацию пользователя. И в случае ошибки в вводе каких-либо данных кидает CustomException. Выглядит код регистрации примерно так: /// <summary> /// Registers the user. /// </summary> /// <param name=”username”>The username.</param> /// <param name=”password”>The password.</param> /// <param name=”email”>The email.</param> public void RegisterUser(string username, string password, string email) { if (/*проверяем username*/) throw new InvalidUsernameException(); if (/*проверяем password*/) throw new InvalidPasswordException(); if (/*проверяем email*/) throw new InvalidEmailException(); ... }

А так его использование: /// <summary> /// Handles the Click event of the btnRegister control. /// </summary> /// <param name=”sender”>The source of the event.</param> /// <param name=”e”>The <see cref=”System.EventArgs”/> instance containing the event data.</param> protected void btnRegister_Click(object sender, EventArgs e) { try { RegisterUser(txtUsernamt.Text, txtPassword.Text, txtEmail.Text); } catch (InvalidUsernameException usernameException) { // даем знать пользователю произошла // ошибка связанная с Username } catch (InvalidPasswordException passwordException) { // даем знать пользователю произошла // ошибка связанная с Password } catch (InvalidEmailException emailException) { // даем знать пользователю что произошла // ошибка связанная с Email }

....

Всё отлично работает, программист знает как обработать ошибку, пользователь знает где ошибка. А теперь представим, что метод RegisterUser у нас habradigest #2 • октябрь 2008

находится в WCF сервисе. Вот что говорит MSDN по поводу исключений в WCF: In all managed applications, processing errors are represented by Exception objects. In SOAP-based applications such as WCF applications, service methods communicate processing error information using SOAP fault messages. SOAP faults are message types that are included in the metadata for a service operation and therefore create a fault contract that clients can use to make their operation more robust or interactive. In addition, because SOAP faults are expressed to clients in XML form, it is a highly interoperable type system that clients on any SOAP platform can use, increasing the reach of your WCF application.

Это означает, что для передачи исключения используется SOAP fault message и с ними надо работать несколько иначе. Т. е. мы можем бросать только FaultException. Но не все так скучно, у FaultException есть generic перегрузка FaultException<T>. Ей мы и воспользуемся. Создадим специальные классы для наших ошибок: /// <summary> /// указывает на ошибку в Username /// </summary> [DataContract] class InvalidUsernameFault { [DataMember] public string CustomError; public InvalidUsernameFault() { } public InvalidUsernameFault(string error) { CustomError = error; } } /// <summary> /// указывает на ошибку в Password /// </summary> [DataContract] class InvalidPasswordFault { [DataMember] public string CustomError; public InvalidPasswordFault() { } public InvalidPasswordFault(string error) { CustomError = error;

4


.NET

} } /// <summary> /// указывает на ошибку в Email /// </summary> [DataContract] class InvalidEmailFault { [DataMember] public string CustomError; public InvalidEmailFault() { } public InvalidEmailFault(string error) { CustomError = error; }

public void RegisterUser(string username, string password, string email) { if (/*проверяем username*/) throw new FaultException<InvalidUsernameFault>( new InvalidUsernameFault(“пользователь Medved уже зарегистрирован”)); if (/*проверяем password*/) throw new FaultException<InvalidPasswordFault>( new InvalidPasswordFault(“пароль ’12345’ недопустим”)); if (/*проверяем email*/) throw new FaultException<InvalidEmailFault>( new InvalidEmailFault(“уже зарегистрирован пользователь с адресом ya@krasafcheg.ru”)); ... }

}

Теперь вернемся к нашему WCF сервису. В интерфейсе мы должны указать FaultContract для нашего метода: [OperationContract] [FaultContract(typeof(InvalidUsernameFault))] [FaultContract(typeof(InvalidPasswordFault))] [FaultContract(typeof(InvalidEmailFault))] void RegisterUser(string username, string password, string email);

Ну а теперь мы может быть уверены что наши исключения дойдут до клиента: /// <summary> /// Registers the user. /// </summary> /// <param name=”username”>The username.</param> /// <param name=”password”>The password.</param> /// <param name=”email”>The email.</param> public void RegisterUser(string username, string password, string email) { if (/*проверяем username*/) throw new FaultException<InvalidUsernameFault>( new InvalidUsernameFault()); if (/*проверяем password*/) throw new FaultException<InvalidPasswordFault>( new InvalidPasswordFault()); if (/*проверяем email*/) throw new FaultException<InvalidEmailFault>( new InvalidEmailFault()); ... }

Или можно бросать FaultException указывая подробное описание ошибки. Удобно, например для логирования: /// <summary> /// Registers the user. /// </summary> /// <param name=”username”>The username.</param> /// <param name=”password”>The password.</param> /// <param name=”email”>The email.</param>

habradigest #2 • октябрь 2008

Обрабатывать такие исключения тоже проще простого: /// <summary> /// Handles the Click event of the btnRegister control. /// </summary> /// <param name=”sender”>The source of the event.</param> /// <param name=”e”>The <see cref=”System.EventArgs”/> instance containing the event data.</param> protected void btnRegister_Click(object sender, EventArgs e) { try { … wcfclient.RegisterUser(txtUsernamt.Text, txtPassword.Text, txtEmail.Text); } catch (FaultException<InvalidUsernameFault> usernameException) { // даем знать пользователю произошла // ошибка связанная с Username } catch (FaultException<InvalidPasswordFault> passwordException) { // даем знать пользователю произошла // ошибка связанная с Password } catch (FaultException<InvalidEmailFault> emailException) { // даем знать пользователю что произошла // ошибка связанная с Email } catch (FaultException faultEx) { // обрабатывает нераспознанную ошибку, // например произошла ошибка авторизации или // ошибка соединения с сервером нашего WCF сервиса } ... }

Как видим, ничего сложного нет! Есть вопросы? напишите, обязательно разберемся ;-) Hope this helps!

5


JAVASCRIPT

JavaScript Cross Site (XSS) POST sirus

Недавно, в Dojo появилась возможность производить cross site POST запросы, т. е. отправка POST запросов на другие сайты, с другими доменными именами. Это событие осталось незамеченным в нашем сообществе JavaScript разработчиков. По крайней мере, никто и слова про это не сказал. А зря… В один прекрасный момент понадобилась возможность осуществлять cross-site POST запросы. Естественно понимая, что на данный момент безопасность браузеров запрещает cross-site AJAX, был выбран вариант использования Flash-AJAX связки. Но все, же лично мне нравятся «чистые» JavaScript решения. Поэтому до конца мы все же не удовлетворились. Недавно утром пребывая в альфа состоянии сна, мне приснился принцип на основе которого, можно осуществлять XSS POST используя только JavaScript. Суть реализации XSS POST основана на особенностях хранения данных в свойстве window. name. Мысли пришли благодаря недавним сериям статей в Инете про window.name. Покопавшись в Google, сразу же нашлась уже готовая реализация. (Видать приснилось не только мне). Dojo как всегда впереди планеты всей www.sitepen.com/ blog/2008/07/22/windowname-transport/. Молодцы.

Как это работает Принцип работы довольно подробно описан по ранее указанной ссылке. В двух словах: если изменить свойство window.name оно остается измененным пока браузер (вкладка-табулятор) открыт. Если изменить свойство на одном сайте, то его можно потом прочитать на другом. Т. е. при переходе от сайта к сайту браузер не очищает window.name. Причем данный механизм хранения значения window.name работает во всех браузерах. XSS POST транспорт, использует эту замечательную возможность. Другими словами: при отправке данных на другой сервер происходит следующее: 1. загружаем страницу с иного сайта/домена 2. эта страница оставляет свой след в window.name 3. возвращаемся на свой сайт/домен 4. считываем оставленный след из window.name Вдобавок, для передачи POST запроса используется трюк c атрибутом target элемента form (принцип AJAX Uploader, Gmail Uploader файлов). Ответ от сайта на который идет запрос должен быть в следующем формате: <script> window.name = ‘тут пишем то, что хотим передать’; </script>

habradigest #2 • октябрь 2008

Соответственно данный подход не позволяет забрать данные без ведома владельца иного сайта, потому что ответ должен быть сформирован в определенном специфическом формате. Это обстоятельство сохраняет безопасность и защищает от XSS скрабинга. Т. е. по сути это чем-то похоже на механизм разрешения кросс-доменных запросов (crossdomain.xml) во Flash.

Более детально 1. Создается iframe и вешается обработчик на событие onload. 2. Если необходим просто GET запрос, то у iframe в атрибуте src устанавливается URL адрес ресурса. Если необходим POST запрос то динамически создается форма. Адрес ресурса уже указывается в атрибуте action объекта формы, а параметры передаются в элементах input (name=имя параметра, value=значение параметра). Чтобы ассоциировать iframe с формой (form) у нее ставится такой же идентификатор (атрибут id) что и у iframe. После генерации формы просто вызывается метод формы submit. Запрос произведен. 3. Функция обратного вызова указанная в iframe срабатывает после того как данные загрузились. В нашем случаем загружается конструкция вида <script>window.name=’данные’</script> и данные попадают в свойство name объекта contentWindow. Задача обработчика достать эти данные. И если вы произведете обращение к iframe.contentWindow. name то будет отказано в доступе. Чтобы обойти эту проблему нужно вернуться в свой домен, делается это сменой значения аттриубута location объекта contentWindow. Например, можно указать адрес на пустой blank.html со своего домена или просто записать «about:blank» (iframe.contentWindow.location = ‘about:blank’). После этого можно считать данные из атрибута name.

Относительно текущей реализации от Dojo Проанализировав их разработку dojox.io.windowName.js мы нашли ряд значительных недостатков: 1. Сильная зависимость от ядра, которое весит довольно «некисло». 2. Необходимость наличия «resources/blank.html» файла.

6


JAVASCRIPT

Вдобавок: 1. нам не понравился код, наше субъективное мнение — грязный код. 2. для FireFox применяется дополнительные «телодвижения» (комментарий в исходниках относительно этого: FF2 allows unsafe sibling frame modification) – честно говоря мы так и не поняли для чего именно… Нам захотелось улучшить текущее состояние XSS POST и, соответственно, написали свою реализацию, которая гораздо меньше по объему, самодостаточная и не привязана ни к одной из библиотек, код более чистый и оптимальный. Сравнивайте и судите сами: * исходный код srax.xss.js * Пример использования

Пример вызова:

SRAX.XSS.post('http://okarta.ru/scripts/post-xhr.php', 'a=a1&b=b2', function(text){ alert(text)})

XSS POST в нашей реализации в дальнейшем будет дорабатываться и развиваться, соответственно обрастет большим количеством методов и параметров. Кому интересно, сможет всегда скачать более новую версию.

habradigest #2 • октябрь 2008

Как всегда существует общий недостаток: при использовании iframe для передачи данных в браузере появляется история, т. е. активируется кнопка «Назад». Может кто знает как вставить iframe без срабатывания истории? Было замечательно устранить этот недостаток.

Зачем это надо Давно существуют механизмы XSS GET запроса данных. И по сей день зачастую их бывает достаточно для решения большего количества задач. Но все же, иногда бывают моменты, когда ограничения GET запросов не позволяют качественно решить нужную задачу. К примеру, Google AJAX Language API реализовано на XSS GET запросах и имеет ограничение на количество переводимых символов за один запрос – 500 символов. Теперь имея XSS POST данное ограничение можно смело убрать. Есть и другие случаи, когда XSS POST жизненно необходим. Грамотным специалистам XSS POST транспорт даже очень пригодится. Автор статьи — Руслан Синицкий, соавтор — Александров К. М., aka

m007

7


КЛИЕНТСКАЯ ОПТИМИЗАЦИЯ

Выносим CSS в пост-загрузку sunnybear, http://webo.in

После сравнительной заметки о CSS Sprites и data:URL все мои мысли были направлены на решение основной проблемы: в общем случае [при использовании data:URL], загрузка страницы не ускорится, а даже может замедлиться, потому что фоновые картинки (включенные через data:URL) будут грузиться в один поток, а не в несколько при обычном использовании спрайтов. Если фоновых картинок достаточно много (несколько десятков Кб), то это окажется существенным.

Постановка задачи При использовании data:URL итоговый CSS-файл занимает довольно большой объем (фактически, размер всех картинок*1,2...1,5 + базовый CSS). И это в виде архива. Если файл не заархивирован, то его дополнительный размер увеличивается многократно (в 2,5–3 раза относительно размера всех фоновых изображений), но это не так существенно, ибо пользователей с отключенным сжатием для CSS-файлов сейчас единицы.

дный объем загружаемого CSS должен быть минимальным (можно также рассмотреть варианты по включению его в сам HTML).

На практике На практике все оказалось не сильно сложнее. Мы загружаем в head страницы (до вызовов любых внешних файлов) наш «легкий» CSS: <link href=«light-light.css» rel=»stylesheet» type=«text/css» media=«all»/>

Что нам, фактически, нужно? Во-первых, нужно разделить весь массив CSS-правил на относящиеся к фоновым изображениям и не относящиеся. Вовторых, сообщить браузерам, что они могут отобразить страницу без первого массива правил (ведь если в нем содержатся только фоновые изображения, то они могут и подождать чуть-чуть).

А затем добавляем в комбинированный window.onload (в самое начало) создание нового файла стилей, который дополняет уже загрузившийся фоновыми изображениями:

Чего мы таким образом добиваемся? Фактически, используя такой подход, мы создаем другой контейнер для фоновых изображений (не ресурсное изображение, а CSS-файл), который удобнее использовать в большинстве случаев. Мы объединяем все фоновые картинки не через CSS Sprites, а через data:URL, и можем загрузить их все одним файлом (в котором каждая картинка будет храниться совсем отдельно). При этом избегаем любых проблем с позиционированием фона. Теоретическое решение

В результате мы имеем максимально быстрое отображение страницы, а затем стадию пост-загрузки, которая вытянет и с сервера все дополнительные картинки (тут уже сам браузер постарается), стилевые правила и скрипты.

Все гениальное просто, поэтому мы можем загружать в начале странице легкий-легкий CSS-файл (безо всяких фоновых изображений, только базовые стили, чтобы только отобразить страницу корректно), потом по комбинированному window.onload грузить в 2—4 потока динамические файлы стилей. Возможные минусы: после загрузки каждого дополнительного CSS-файла будет перерисовка страницы. Если таких файлов всего 1 или 2, то отображение страницы произойдет значительно быстрее. Почему мы не распараллелим загрузку файлов стилей в самом начале документа? Потому что два файла будут загружаться медленнее, чем один. К тому же мы ратуем за максимально быстро отображение страницы в браузере пользователя, поэтому исхоhabradigest #2 • октябрь 2008

function combinedWindowOnload() { load_dynamic_css(«background-images.css»); ... }

А доступность? Внимательные читатели уже заготовили вопрос: а что, если у пользователя отключен JS? Ну, тут должно быть все просто: мы добавляем соответствующий noscript для поддержки таких пользователей. С маленьким нюансом: noscript не может находиться в head, а link не может находиться в body. Если мы соблюдаем стандарты (все же иногда лучше довериться профессионалам и не ставить браузеры в неудобное положение, когда они встретятся с очередным отклонением от спецификации), то стоит искать обходные пути. После небольших экспериментов с коллекцией styleSheets и другими DOM-объектами, было выделено следующее изящное решение, обеспечивающее 8


КЛИЕНТСКАЯ ОПТИМИЗАЦИЯ

работу схемы во всех браузерах (замечание: после многоличсленных экспериментах было решено остановиться на HTML-комментариях: они оказались наилучшим способом запретить загрузку указанного CSS-файла):

намического CSS-файла с фоновыми картинками после срабатывания window.onload (который, на самом деле, срабатывает раньше, чем во всех остальных браузерах) придумать не удалось. Зато первоначальная картинка в браузере появляется быстрее.

<script type=»text/javascript»> /* если мы сможем создать динамический файл стилей */ if (document.getElementsByTagName) { /* то добавляем в загрузку облегченную версию */ document.write('\x3clink href=»light-light.css» rel=»stylesheet» type=»text/css» media=»all»/>'); /* после этого начинаем HTML-комментарий */ document.write('\x3c--'); } </script> <link href=»full.css» rel=»stylesheet» type=»text/css» media=»all»/>

Итак, если брать сразу комбинированный window. onload и добавлять в него загрузку стилей, то получится примерно такая конструкция:

<!--[if IE]><![endif]-->

В результате брайзер с включенным JavaScript запишет начало комментария, а закроет его только после <link> (комментарии не могут быть вложенными). При выключенном JavaScript <script> не отработает, <link> обработается и добавится в очередь загрузки, а последний комментарий будет просто комментарием. Почему комментарии являются еще и условными комментариями для IE? Просто потому что на обычные пустые комментарии Firefox отреагировал странно, и я сейчас нахожусь в поисках кроссбраузерного решения.

Грабли, грабли, грабли... В ходе тестирования в Internet Explorer обнаружилось, что если добавлять файл стилей сразу параллельно со скриптами (в функции, которая для него срабатывает по onreadystatechange), то IE «морозит» первоначальную отрисовку страницы (т.е. показывает белый экран), пока не получит «свеженький» файл стилей. Для него пришлось вставить фиктивную задержку следующим образом: setTimeout('load_dynamic_css(«background-images.css»)',0);

В Safari же логика отображения страницы в зависимости от загружаемых файлов отличается ото всех браузеров. Подробнее можно прочитать эту заметку о FOUC проблеме и возможных решениях. Если в двух словах, то можно жестко определить начальный набор файлов, необходимых для отображения страницы на экране (HTML/CSS/JS), а можно начать загружать все файлы в порядке приоритетности (и выполняя все их зависимости) и проверять время от времени, можно ли уже отобразить страницу (выполняя все вычисления в фоновом режиме без обновления экрана). В общем, у Safari второй подход, поэтому ничего лучше выноса загрузки диhabradigest #2 • октябрь 2008

/* объявляем функцию по динамической загрузке стилей и скриптов первый параметр - путь к файлу, второй - тип файла К сожалению, функция объявлена глобальна */ function loadDynamic (src,type){ var node=document.createElement(type?»link»:»script»); node = document.getElementsByTagName(«head»)[0]. appendChild(node); /* если передаем второй параметр, то это таблица стилей */ if(type){ node.setAttribute(«rel»,»stylesheet»); node.setAttribute(«media»,»all») } node.setAttribute(«type»,»text/»+(type?»css»:»javascript»)); node.setAttribute(type?»href»:»src»,src) } (function(){ /* немного модифицированная версия обработчика http://webo.in/articles/habrahabr/05-delayed-loading/ */ function combinedWindowOnload(){ if(arguments.callee.done){return} arguments.callee.done=true; if(document.getElementsByTagName){ /* если не Safari, то загружаем CSS с фоновыми изображениями динамически */ if(!/WebKit/i.test(navigator.userAgent)){ /* для обхода IE добавляем псевдо-задержку */ setTimeout('loadDynamic(«background-images. css»,1)',0); } /* ставим на поток загрузку всех наших скриптов */ loadDynamic(«some_scripts.js»); } } /* дальше идет стандартный кроссбраузерный код с http://webo.in/articles/habrahabr/05-delayed-loading/ */ ...

9


КЛИЕНТСКАЯ ОПТИМИЗАЦИЯ

/* навешиваем на window обработчик по событию Onload, спасибо lusever за компактный вид http://webo.in/articles/livejournal/01-native-browser-events/ */ window[/*@cc_on !@*/0?'attachEvent':'addEventListener'](/*@ cc_on 'on'+@*/'load',function(){ /* если Safari, то загружаем наконец этот CSS */ if(/WebKit/i.test(navigator.userAgent)){ combinedWindowOnload.loadDynamic(«backgroundimages.css»,1); } /* добавочный вызов WindowOnload для «старых» браузеров */ combinedWindowOnload() },false) })()

Выигрыш При наличии у вас большого количества маленьких декоративных фоновых изображений (например, для webo.in на каждой странице используется от 30 до 40 различных картинок, общий объем которых составляем порядка 30Кб), которые к тому же могут повторяться по различных направлениям, может быть очень удобно объединить их все в один файл, который загружать после отображения страницы на экране.

По-прежнему отдельным файлом загружается CSS Sprites в 19Кб. Вся стадия предзагрузки отображается, учитывая пожатый размер HTML в 24,5Кб: (DNS) + (TCP/IP) + (HTML) + (CSS) ~= 100мс + 100мс + 245мс + 125 мс ~= 570мс. Выигрыш при применении метода составит 12%. Все расчеты проведены для главной страницы webo.in. Если у кого-то появятся мысли по поводу Safari или общей производительности, пожалуйста, напишите в комментариях. P.S. я посмотрел решение от YUI по загрузке файлов стилей. Оно решат другую задачу: как вообще загрузить несколько дополнительных CSS-файлов на страницу, а не как загрузить их максимально быстро.

Читать дальше: ——Способы ускорения загрузки Вашего сайта ——CSS Sprites зло, не используйте их! ——Кроссбраузерное использование data:URL ——Картинки в теле страницы с помощью data:URL ——CSS Sprites: все, что вы знали, но боялись спросить

Описанная техника (кроссбраузерный data:URL + динамическая загрузка файлов стилей) позволяет добиться всех преимуществ технологии CSS Sprites, не затягивая загрузку страницы. При этом обладает очевидными плюсами: не нужно лепить все картинки в один файл, можно работать с каждой совершенно отдельно, что позволяет добиться большей семантичности кода и большего удобства использования сайтов. к тому же это несколько сократит CSSкод за счет уничтожения необходимости использовать background-position.

Примеры загрузки Во всех CSS файлы добавлена некоторая задержка для демонстрации функционирования метода на медленных соединениях (или при больших объемах файлов). * Один CSS-файл, что соответствует обычной ситуации при использовании data:URL. * CSS-картинки в пост-загрузке (по событию window.onload). * CSS-картинки в пост-загрузке (по комбинированному событию window.onload). Выигрыш на реальном соединении (стадия предзагрузки страницы) будет равен размеру всех CSSизображений (для webo.in этот код составляет 26Кб в непожатом и 8,5Кб в архивированном виде, соответственно, для 100Кб/с это будет 260..85мс). habradigest #2 • октябрь 2008

10


ВЕБ 2.0

Лучшие графические редакторы, написанные на Flash veter

Когда заходит речь о миграции с Windows на другие платформы или на веб-приложения, каждый раз возникает неоспоримый аргумент: но там же нет фотошопа! Этот легендарный графический пакет благодаря пиратам прижился, наверное, на подавляющем большинстве компьютеров в СНГ. И несомненно альтернативу столь сложной программе найти сложно. Когда заходит речь о миграции с Windows на другие платформы или на веб-приложения, каждый раз возникает неоспоримый аргумент: но там же нет фотошопа! Этот легендарный графический пакет благодаря пиратам прижился, наверное, на подавляющем большинстве компьютеров в СНГ. И несомненно альтернативу столь сложной программе найти сложно. Но если откровенно, как в большинстве случаев используется вся мощь фотошопа? Изменить размеры, подкорректировать яркость, иногда убрать лишние детали, да отправить на е-мейл подружке. Нужен ли для этого полноценный большой Photoshop? В большинстве случаев нет, если только вы не дизайнер-профессионал высокого класса. А значит, аналог найти будет уже проще. Но искать аналог среди настольных программ или программ под Linux это неинтересно! Мы живем в век Web 2.0, поэтому попробуем поискать аналоги среди веб-приложений. Да, еще недавно это было бы фантастикой, но развитие графических возможностей прежде всего Flash уже сегодня позволяет получить весьма интересные результаты. Конечно, описываемые редакторы не станут полноценной заменой «старшему брату», но для небольших фотокоррекций они пригодятся. Или представьте себя в интернет-кафе, где привычного вам софта нет, а вам нужно срочно взять готовую картинку, что-то в ней поменять, отметить или подписать и отправить собеседнику. В этом случае вас тоже может спасти одна из рассматриваемых ниже программ.

72photos

http://72photos.com

Это даже не редактор, а скорее фотогалерея, дающая возможность слегка подредактировать сохраненные снимки. Рисования нет. Можно только наложить эффекты на снимок, подправив цветопередачу, яркость, да обрезав лишнее. В наличии также размытие/резкость да пара стандартных фильтров. Все достаточно среднее, ничего выдающегося, но как интересное применение возможностей Flash подойдёт. Без регистрации не работает, поддерживает бесhabradigest #2 • октябрь 2008

72photos

платную и платную версии. Последняя отличается увеличенными лимитами дискового пространства и траффика. Из полезных особенностей стоит отметить возможность работы с картинками, сохраненными на других фотохостингах, впрочем, из очень небольшого списка.

FotoFlexer

http://fotoflexer.com/

Ну, для начала — это абсолютный лидер по реализованным функциям. Он поддерживает наибольшее количество фотохостингов, с которых можно загрузить фотографию для редактирования, возможна также загрузка просто по URL, что позволит отредактировать вообще любую картинку, загрузка картинок с компьютера пользователя тоже наличествует. С сохранением сложнее. Чтобы сохранять файл, куда вам вздумается, придется регистрироваться. И даже просто так на компьютер файл не отдадут. Придется выдирать с какой-то файлопомойки. Или регистрироваться. Мелочь, но неприятно. Огромное количество эффектов и инструментов. Тут даже некоторые традиционные редакторы пасуют. Рисуйте, накладывайте эффекты, трансформируйте картинку. Есть даже готовые для добавления графические примитивы, позволяющие создавать 11


ВЕБ 2.0

Fotoflexer

Phoenix

столь любимые народом фотографии со вставкой лиц друзей, например, на доллары. Или пририсовывать их неизвестному бодибилдеру. Для этого теперь не придется ехать в Сочи или идти в фотостудию! :) Здесь же можно и рамочки прикрутить и постер оформить. В общем, раздолье для бытового фотографа. Разве что подписать «это мы с Коляном» не получится. Русский текст программа напрочь не понимает. При попытке ввести русский текст, я получил тот же текст… в английской раскладке.

Photoshop Express

Есть инструменты и для более серьезной публики. Сложные трансформации, инструменты выделения и перекрашивания, аналогичные привычным инструментам, даже кое-какая поддержка слоев. Правда, за всю эту перенавороченность приходится расплачиваться наличием мелких глюков и недочетов. То окошко не то вылезет, то рамочка так и останется на экране, хотя соответствующий инструмент уже закрыт, то какие-то непонятные элементы выскочат. Ну и баннер на всю ширину экрана в самом верху несколько раздражает. Впрочем, приходится мириться. Аналогов по функциональности среди онлайн-сервисов на сегодня нет.

Phoenix

https://www.photoshop.com/express/

Казалось бы, вот он! Лидер, которого все так ждали. Распиаренный аналог настольного Photoshop от создателей Photoshop. Но есть очень много нюансов. Во-первых сначала придется пройти регистрацию. Во вторых, мы увидим не версию легендарного Photoshop, а скорее аналог вышеупомянутого 72photos, только более качественно исполненный. Это тоже фотохостинг, причем социальный. Вы можете просматривать галереи других пользователей, даже комментировать их. При этом интеграции со сторонними сервисами нет, работать можно только с фотографиями, загруженными с компьютера пользователя. Возможности же собственно редактора скромны. Изменение размеров, повороты, немного эффектов. Рисования опять-таки нет. Впрочем, имеющиеся функции реализованы качественно, работает все тоже очень шустро. Сказывается, что авторы разрабатывали не только Photoshop, но и собственно Flash. Получившимся продуктом приятно пользоваться. И, думаю, следует ожидать дальнейшего развития.

http://a.viary.com/tools/phoenix

Очень симпатичный редактор с приятным, продуманным интерфейсом. Без регистрации работать отказывается. Зато потом способен открывать файлы не только со своего сервера и компьютера пользователя, но и со сторонних сервисов и по URL. После открытия файла мы получаем весьма приятный редактор с неплохим набором фильтров и эффектов. Есть и полноценное рисование, даже с возможностью русских надписей. Поддерживаются слои. Слегка огорчает серверная часть, которая любит давать сбои при сохранении. Да и само приложение могло бы работать побыстрее. В остальном же — отличный редактор. habradigest #2 • октябрь 2008

Photoshop Express

12


ВЕБ 2.0

PicMagick

Pixer.us

Элементарный и самый, пожалуй, примитивный из рассматриваемых редакторов. Позволяет только загрузить картинку с компьютера, наложить стандартный набор эффектов и получить обратно свою фотографию.

Данный редактор включен в обзор скорее ради красивого числа обозреваемых сервисов. Это в общем-то и не Flash-редактор вовсе. Он написан на Javascript, что уже само по себе интересно. Ну и на полноценный редактор сервис не тянет.

Возможностей сохранения на сервере, рисования и интеграции с другими сервисами нет. Минимализм в чистом виде. Даже регистрации нет. Редактор сразу готов к работе. При этом особой скоростью работы не отличается.

Вы можете загрузить фотографию со своего компьютера, применить к ней ряд эффектов и сохранить обратно. И всё. Работает все достаточно шустренько, но без изысков. Рисования нет как класса.

http://www.picmagick.com/

Единственная особенность, выделяющая из ряда прочих редакторов — фильтр коррекции кожи. Впрочем, достоинство сомнительное, того же можно добиться и другими инструментами.

http://pixer.us/

Но как демонстрация возможностей безфлешевых технологий, редактор интересен. К примеру, если сравнивать его с Picmagick, то поединок будет как минимум равным.

Picnik

http://www.picnik.com/

Этот редактор примечателен тем, что именно его используют на популярнейшем фотохостинге Flickr в качестве стандартного. А значит, что-то в нем есть. Впрочем, никто не мешает пользоваться этим редактором и всем желающим. Даже без регистрации. И закачать картинку можно хоть с компьютера, хоть из популярного фотохостинга или блога, хоть с любого URL. Редактор понимает не только стандартные web-типы графики, но и большое количество других распространенных форматов. По этому параметру Picnik — абсолютный лидер. Хорош он и в реализации. Достаточно долгий старт приложения компенсируется большим количеством эффектов и инструментов. Несколько уступая FotoFlexer, он опережает остальных конкурентов. Вот только рисования в чистом виде — нет. Можно только накладывать готовые графические примитивы. Ну и когда я попытался ввести русский текст, получил сплошь вопросительные знаки. Такая вот ложка дегтя в неплохом в целом редакторе.Сохранять полученный результат можно тоже как у себя на компьютере, так и на популярных хостингах. А можно и сразу на е-мейл. Причем в разнообразных форматах. habradigest #2 • октябрь 2008

Picnik

Pixlr

http://www.pixlr.com/app/?loc=ru

Это опять редактор в чистом виде. Никакого вам хостинга. Зато есть API, что возможно позволит встраивать редактор в сторонние приложения. Впрочем, исследование API выходит за рамки моего обзора. Итак, перед нами один из семейства клонов фотошопа. Есть русская локализация (!), чем не могут похвастать другие редакторы. Соответственно, русские надписи делать тоже можно. Отличная оптимизация работы. Все открывается быстро. 13


ВЕБ 2.0

Pixlr

Splashup

Отличный функционал. Есть даже столь любимый Magic Wand. Слои и фильтры тоже на месте, хотя набор инструментов мог бы быть побольше. Впрочем, все основные примочки на месте. Да и интерфейс опять-таки навевает воспоминания о фотошопе. Приятный в работе, продуманный редактор.

рван от большого интернета. Ни загрузки по URL, ни интеграции с другими сервисами вы не найдете. Только аккаунт на собственном сервере и загрузка с компьютера. Последняя у меня, впрочем, сработала лишь на одном компьютере. Возможно финский сервер просто недостаточно быстр, чтобы оперативно обрабатывать закачки.

Splashup

http://www.splashup.com/splashup/

Очередной представитель гибрида фотохостинга и неплохого редактора. Собственно фотохостинг я не оценивал, а вот редактор мне понравился. Опять слои, опять эффекты, опять полноценное рисование. Русский язык не работает, а вот все остальное на достойном уровне. Можно загрузить свою картинку как с компьютера, так и из интернета (поддержка фотохостингов и прямых URL), отредактировать и сохранить опятьтаки в различные места. Очень удобный, приятный интерфейс, отличная скорость работы. Достойный представитель онлайнредакторов, пытающихся копировать Photoshop.

SUMO Paint

http://www.sumopaint.com/

Но зато функционал впечатляет. Тут нет попсовых рамочек и цветочков, только серьезные взрослые инструменты. Полноценная поддержка слоев, разнообразные эффекты и фильтры. Чем-то напомнило старые версии Photoshop. Очень и очень достойная разработка. И даже поддержка русских шрифтов в наличии. Если поправят проблемы с загрузкой фотографий, будет совсем замечательно.

Заключение Да, никто из онлайн-редакторов не составляет и вряд ли составит конкуренцию Photoshop. Если в базовых инструментах как-то еще можно соперничать, то когда речь заходит о сложных фильтрах, Photoshop не оставляет конкурентам никаких шансов. А уж представить себе в онлайн варианте пакетную обработку или плагины и вовсе невозможно. И, разумеется, профессионалы останутся верны привычному инструменту. Зато для обычных пользователей возможности онлайн-редакторов уже сегодня могут вполне пригодиться. Кстати, любопытно, что почти все редакторы созданы либо энтузиастами, либо мало кому известными компаниями. Что очередной раз подтверждает: в Web 2.0 даже мелкие команды могут бросать вызов крупным корпорациям. Старые заслуги никого не спасают. И не боюсь ошибиться, предполагая, что в обозримом будущем похожие онлайн-редакторы начнут встраивать в свои сервисы все мало-мальски крупные фотохостинги и социальные сети. Поэтому будущее у этого направления весьма светлое.

Очень солидный редактор. Очень близок к настольному приложению как по внешнему виду, так и по функционалу. Правда в качестве компенсации отоhabradigest #2 • октябрь 2008

14


БЛОГ ИМ. DNOVIKOV

Простые вещи: подгрузка через AJAX HTML-кода, содержащего JavaScript DNovikov

При разработке CMS S.Builder наша команда активно использовала AJAX. Теперь вот решили поделиться накопленным опытом. Начнем с этого хабратопика. Не буду здесь затрагивать различные фреймворки и библиотеки. Свой код всегда роднее. Для работы с AJAX-ом в S.Builder написана библиотека sbAJAX. Можете качать и пользоваться :). В этом файле есть функция sbEvalJS. Для тех, кто не знает, объясню. При подгрузке через AJAX и вставке на страницу HTML-кода, содержащего JavaScript, JavaScript выполняться не будет или полезут баги. Эта функция как раз решает поставленную задачу. После загрузки, полученный код передается функции, она вырезает все что находится между тегами <script …> … </script> и, если это не вынесенный в файл JavaScript, прогоняет его через eval(), либо подгружает файл, содержащий JavaScript, и прогоняет через eval() полученный код. Все просто. Код функции приведен ниже: function sbExecScript(text) { if (!text) return; if (window.execScript) { window.execScript(text); } else { var script = document.createElement(‘script’); script.setAttribute(‘type’, ‘text/javascript’); script.setAttribute('language', 'JavaScript'); if (_isIE) script.text = text; else script.appendChild( document.createTextNode( text ) ); var head = document.getElementsByTagName(“head”)[0] || document.documentElement; head.insertBefore( script, head.firstChild ); head.removeChild( script ); } return; } var sbEvalJSSrcs = [];

habradigest #2 • октябрь 2008

function sbEvalJS(s) { var js_ScriptFragment = ‘(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)’; var js_ScriptSrcFragment = ‘<script.+(src[ ]*=[ ]*\’(.*?)\’|src[ ]*=[ ]*”(.*?)”).+’; var matchAll = new RegExp(js_ScriptFragment, ‘img’); var matchOne = new RegExp(js_ScriptFragment, ‘im’); var matchSrc = new RegExp(js_ScriptSrcFragment, ‘im’); var arr = s.match(matchAll) || []; var JSCode = []; for (var i = 0; i < arr.length; i++) { var srcMt = arr[i].match(matchSrc); if (srcMt) { if (srcMt.length > 3) srcMt = srcMt[3]; else srcMt = srcMt[2]; if (srcMt != '') { var found = false; for (var j = 0; j < sbEvalJSSrcs.length; j++) { if (sbEvalJSSrcs[j] == srcMt) { found = true; break; } } if (found) continue; sbEvalJSSrcs[sbEvalJSSrcs.length] = srcMt; var res = sbLoadSync(srcMt); if (res) JSCode[JSCode.length] = res; } } var mtCode = arr[i].match(matchOne); if (mtCode && mtCode[1] != '')

15


БЛОГ ИМ. DNOVIKOV

JSCode[JSCode.length] = mtCode[1]; } s = s.replace(matchAll, ‘’); for(var i = 0; i < JSCode.length; i++) sbExecScript(JSCode[i]); return s; }

Функция возвращает HTML-код без JavaScript, его и вставляем в нужное место страницы, чтобы избежать повторного выполнения скриптов под IE, например. Если будете пользоваться нашей библиотекой, то Вам могут оказаться полезными следующие функции: sbLoadSync(url) – синхронно подгружает содержимое указанного URL-а и возвращает его.

habradigest #2 • октябрь 2008

sbLoadAsync(url, pfunction) — асинхронно подгружает содержимое указанного URL-а и передает его как параметр в функцию pfunction. sbSendForm(form) — синхронно отправляет данные формы POST-запросом и возвращает результат скрипта, прописанного в action формы. sbSendFormAsync(form, pfunction) — асинхронно отправляет данные формы POST-запросом и передает результат скрипта, прописанного в action формы, как параметр в функцию pfunction. Замечу, что у всех функций sbEvalJS вызывается сразу при получении ответа от сервера, а возвращаемый ими результат не содержит JavaScript. Если логику надо изменить, уберите везде return sbEvalJS(res);. Библиотека тестировалась в IE 6, IE 7, FireFox 2, FireFox 3, Opera 9.5, Safari 3.0, Google Chrome 0.3. Ну вот и все, для первого раза достаточно :)

16


MYSQL

Оптимизация MySQL запросов tuta_larson авторство этой статьи принадлежит mysqlconsulting.ru

В повседневной работе приходится сталкиваться с довольно однотипными ошибками при написании запросов. В этой статье хотелось бы привести примеры того, как НЕ надо писать запросы.

Выборка всех полей

2. Вставки

SELECT * FROM table

При написании запросов не используйте выборку всех полей — «*». Перечислите только те поля, которые вам действительно нужны. Это сократит количество выбираемых и пересылаемых данных. Кроме этого, не забывайте про покрывающие индексы. Даже если вам на самом деле необходимы все поля в таблице, лучше их перечислить. Во-первых, это повышает читабельность кода. При использовании звездочки невозможно узнать какие поля есть в таблице без заглядывания в нее. Во-вторых, со временем количество столбцов в вашей таблице может изменяться, и если сегодня это пять INT столбцов, то через месяц могут добавиться TEXT и BLOB поля, которые будут замедлять выборку.

Запросы в цикле Нужно четко представлять себе, что SQL — язык, оперирующий множествами. Порой программистам, привыкшим думать терминами процедурных языков, трудно перестроить мышление на язык множеств. Это можно сделать довольно просто, взяв на вооружение простое правило — «никогда не выполнять запросы в цикле». Примеры того, как это можно сделать: 1. Выборки $news_ids = get_list('SELECT news_id FROM today_news '); while($news_id = get_next($news_ids)) $news[] = get_row('SELECT title, body FROM news WHERE news_id = '. $news_id);

Правило очень простое — чем меньше запросов, тем лучше (хотя из этого, как и из любого правила, есть исключения). Не забывайте про конструкцию IN().

$log = parse_log(); while($record = next($log)) query('INSERT INTO logs SET value = '. $log['value']);

Гораздо более эффективно склеить и выполнить один запрос: INSERT INTO logs (value) VALUES (...), (...)

3. Обновления Иногда бывает нужно обновить несколько строк в одной таблице. Если обновляемое значение одинаковое, то все просто: UPDATE news SET title='test' WHERE id IN (1, 2, 3).

Если изменяемое значение для каждой записи разное, то это можно сделать таким запросом: UPDATE news SET title = CASE WHEN news_id = 1 THEN 'aa' WHEN news_id = 2 THEN 'bb' END WHERE news_id IN (1, 2)

Наши тесты показывают, что такой запрос выполняется в 2-3 раза быстрее, чем несколько отдельных запросов.

Выполнение операций над проиндексированными полями

SELECT user_id FROM users WHERE blogs_count * 2 = $value

Приведенный код можно написать одним запросом:

В таком запросе индекс использоваться не будет, даже если столбец blogs_count проиндексирован. Для того, чтобы индекс использовался, над проиндексированным полем в запросе не должно выполняться преобразований. Для подобных запросов выносите функции преобразования в другую часть:

SELECT title, body FROM today_news INNER JOIN news USING(news_id)

habradigest #2 • октябрь 2008

SELECT user_id FROM users WHERE blogs_count = $value / 2;

17


MYSQL

Аналогичный пример: SELECT user_id FROM users WHERE TO_DAYS(CURRENT_DATE) — TO_DAYS(registered) <= 10;

не будет использовать индекс по полю registered, тогда как SELECT user_id FROM users WHERE registered >= DATE_SUB(CURRENT_DATE, INTERVAL 10 DAY);

будет.

Выборка строк только для подсчета их количества $result = mysql_query(«SELECT * FROM table», $link); $num_rows = mysql_num_rows($result);

Если вам нужно выбрать количество строк, удовлетворяющих определенному условию, используйте запрос SELECT COUNT(*) FROM table, а не выбирайте все строки лишь для того, чтобы подсчитать их количество.

Выборка лишних строк

$result = mysql_query(«SELECT * FROM table1», $link); while($row = mysql_fetch_assoc( $result) && $i < 20) { … }

Если вам нужны только n строк выборки, используйте LIMIT, вместо того, чтобы отбрасывать лишние строки в приложении.

Использование ORDER BY RAND() SELECT * FROM table ORDER BY RAND() LIMIT 1;

Если в таблице больше, чем 4-5 тысяч строк, то ORDER BY RAND() будет работать очень медленно. Гораздо более эффективно будет выполнить два запроса: Если в таблице auto_increment'ный первичный ключ и нет пропусков: $rnd = rand(1, query('SELECT MAX(id) FROM table')); $row = query('SELECT * FROM table WHERE id = '.$rnd);

либо: $cnt = query('SELECT COUNT(*) FROM table'); $row = query('SELECT * FROM table LIMIT '.$cnt.', 1');

habradigest #2 • октябрь 2008

что, однако, так же может быть медленным при очень большом количестве строк в таблице.

Использование большого количества JOIN'ов SELECT v.video_id a.name, g.genre FROM videos AS v LEFT JOIN link_actors_videos AS la ON la.video_id = v.video_id LEFT JOIN actors AS a ON a.actor_id = la.actor_id LEFT JOIN link_genre_video AS lg ON lg.video_id = v.video_id LEFT JOIN genres AS g ON g.genre_id = lg.genre_id

Нужно помнить, что при связи таблиц один-ко многим количество строк в выборке будет расти при каждом очередном JOIN'е. Для подобных случаев более быстрым бывает разбить подобный запрос на несколько простых.

Использование LIMIT SELECT… FROM table LIMIT $start, $per_page

Многие думают, что подобный запрос вернет $per_page записей (обычно 10-20) и поэтому сработает быстро. Он и сработает быстро для нескольких первых страниц. Но если количество записей велико, и нужно выполнить запрос SELECT… FROM table LIMIT 1000000, 1000020, то для выполнения такого запроса MySQL сначала выберет 1000020 записей, отбросит первый миллион и вернет 20. Это может быть совсем не быстро. Тривиальных путей решения проблемы нет. Многие просто ограничивают количество доступных страниц разумным числом. Также можно ускорить подобные запросы использованием покрывающих индексов или сторонних решений (например sphinx).

Неиспользование ON DUPLICATE KEY UPDATE $row = query('SELECT * FROM table WHERE id=1'); if($row) query('UPDATE table SET column = column + 1 WHERE id=1') else query('INSERT INTO table SET column = 1, id=1');

Подобную конструкцию можно заменить одним запросом, при условии наличия первичного или уникального ключа по полю id:

18


MYSQL

INSERT INTO table SET column = 1, id=1 ON DUPLICATE KEY UPDATE column = column + 1

Читайте оригинал статьи на MySQL Consulting. P.S. пишите в личку темы статей по MySQL, которые вы хотели бы прочитать.

habradigest #2 • октябрь 2008

19


MOOTOOLS

Делаем вращательный регулятор Yarc

Этим топиком я продолжаю цикл статей о написании всяких вкусностей для MooTools. Сегодня мы на чистом JavaScript сделаем вращательный регулятор — контрол, который часто используют в работающих со звуком программах для регулировки громкости или баланса. Вот примерно такой:

Так как с помощью JavaScript нельзя вращать изображения, то мы поступим не так как поступили бы во флеше. Для реализации задачи нам понадобятся два изображения:

Идея в том, чтобы поместить индикатор на подложку и вращать его в соответствии с положением курсора мыши. Для вычисления положения индикатора нам пригодится математика. Не бойтесь, волновое уравнение решать нам не прийдется, вспомим немного школьной тригонометрии и полярную систему координат. Итак, полярная система координат определяет положение точки по двум величинам: угол поворота радиусвектора относительно полярной оси φ и длина радиусвектора r. Для лучшего понимания взгляните на схему ниже.

Ок, с этим разобрались. Но если r известна изначально (мы знаем размеры картинки), то как же определить угол φ из положения курсора мыши? Опять нам на помощь приходит школьная тригонометрия. Если взглянуть на следующую схему, то можно заметить, что вектор от полюса до точки положения курсора со своими проекциями образует прямоугольный треугольник. Катетами в этом треугольнике будут координаты курсора. Отсюда мы можем найти тангенс угла φ как отношение противолежащего катета к прилежащему:

Жаль, что визуализировать точку в такой системе координат браузеру не под силу — он работает с декартовой системой. Но это не проблема, т. к. мы можем легко перейти из полярной системы в декартову используя формулы: x = r * cos(φ) y = r * sin(φ) Кооринаты x и y мы подставим в CSS-свойства left и top индикатора. Стоит отметить, что на схеме ось y направлена снизу вверх. В браузере же у нее противоположное направление. Это мы учтем, когда будем менять координаты индикатора.

habradigest #2 • октябрь 2008

20


MOOTOOLS

tg(φ) = y / x Угол φ отсюда находим как арктангенс: φ = arctg(tg(φ)) = arctg(y / x) Из математики это, пожалуй, все. Теперь займемся кодингом. В HTML все предельно просто: <div id=»Container»> <div id=»Indicator»></div> </div>

CSS: #Container { position: relative; background-image: url(‘./images/rheostat.png’); width: 64px; height: 64px; } #Indicator { position: absolute; background-image: url(‘./images/indicator.png’); width: 4px; height: 4px; visibility: hidden; }

JavaScript код написан с использованием фреймворка MooTools: var Rheostat = new Class({ Implements: [Events, Options], // Задаем опции по умолчанию. options: { radius: 27, minValue: 0, maxValue: 100 }, // Константы для конвертирования градусов в // радианы и наоборот. deg2rad: Math.PI / 180, rad2deg: 180 / Math.PI, // Флаг, указывающий на то, что мы зажали // левую кнопку мыши. captured: false, // Коструктор initialize: function(container, indicator, options){ this.setOptions(options); this.indicator = $(indicator); this.container = $(container); // Показываем спрятанный по умолчанию индикатор.

habradigest #2 • октябрь 2008

this.indicator.fade(‘show’); // Добавляем обработку событий мыши. this.container.addEvent(‘mousedown’, this.captureMouse.bind(this)); document.addEvents({ ‘mousemove’: this.updateAngle.bind(this), ‘mouseup’: this.releaseMouse.bind(this) }); // Размер контейнера нам нужен для того, чтобы // сместить систему координат индикатора // из левого верхнего угла контейнера в его центр. var containerSize = this.container.getSize(); var indicatorSize = this.indicator.getSize(); this.offset = { x: Math.floor(containerSize.x / 2) - Math. floor(indicatorSize.x / 2), y: Math.floor(containerSize.y / 2) - Math. floor(indicatorSize.y / 2) }; this.angle = 0; this.updateIndicatorPosition(); }, // Запоминаем, что контрол захвачен мышью. captureMouse: function(){ this.captured = true; }, // Стираем флаг захвата. releaseMouse: function(){ this.captured = false; }, // В этом методе считается угол по положению //курсора мыши. updateAngle: function(e){ if (this.captured) { var containerPosition = this.container.getPosition(); // Катеты нашего треугольника. // К mouseLeft я прибавил 0.1 для того, чтобы // избежать возможного деления на ноль // впоследствии. var mouseLeft = e.client.x - this.offset.x containerPosition.x + 0.1; var mouseTop = this.offset.y - e.client.y + containerPosition.y; // Вычисление угла (т.к. Math.atan() возвращает // значение в радианах, // для более простого оперирования с ним // переведем его в градусы). this.angle = Math.atan(mouseTop / mouseLeft) * this.rad2deg; // Т.к. функция арктангенса может вернуть нам // значения только от -90 до +90, то // если курсор находится в левой половине к // углу прибавим 180. Иначе в левой половине

21


MOOTOOLS

// мы индикатор никогда не увидим. if (mouseLeft < 0) this.angle += 180; // Еще одна проверка, чтобы сплошную // последовательность значений от 0 до 360 градусов. if (this.angle < 0) this.angle += 360; // Считаем значение в соответствии с заданными // минимальным и максимальным значениями // регулируемой величины. var value = Math.floor((this.options.maxValue - this. options.minValue) * this.angle / 360 + this.options. minValue); this.fireEvent('valueChanged', value) this.updateIndicatorPosition(); } }, updateIndicatorPosition: function(){ // Переводим угол в радианы для передачи его в // Math.cos() и Math.sin(). var radAngle = this.angle * this.deg2rad var left = this.options.radius * Math.cos(radAngle) + this.offset.x; // Обратите внимание на знак «-». // Этим мы разворачиваем ось y наоборот. var top = -this.options.radius * Math.sin(radAngle) + this.offset.y;

habradigest #2 • октябрь 2008

// Позиционирование индикатора. this.indicator.setStyle(‘left’, left); this.indicator.setStyle(‘top’, top); } });

Конструируется экземпляр этого регулятора так: var rheostat = new Rheostat(‘Container’, ‘Indicator’);

Используем событие изменения значения: rheostat.addEvent(‘valueChanged’, function(value){ // В value попадет текущее значение // регулируемого параметра. });

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

22


WORDPRESS

Создаем тему для Wordpress Aesma

Прежде чем начинать — полезная ссылка: Шпаргалка для дизайнеров Итак. Если мне позволят зеленые циферки именуемые здесь «кармой», то это будет статья разделенная на неопределеное количество частей, в которой я наглядно покажу как можно сделать (код+верстка+оптимизация) свою собственную тему оформления для WP. Что-то я объясню, что-то просто наглядно покажу.

ЧАСТЬ ПЕРВАЯ «Сделать чтобы оно хоть как-то работало»

3. Ну и конечно же файлы не должны быть пустыми.

<!-- шапка --> <?php get_header(); ?> <!-- начало ленты записей --> <?php if (have_posts()) : while (have_posts()) : the_post(); ?> <!-- заголовок записи --> <h2><a href=«<?php the_permalink() ?>»><?php the_title(); ?></a></ h2> <!-- текст записи --> <?php the_content(‘Дальше »’); ?> <!-- конец ленты записей --> <?php endwhile; ?> <?php next_posts_link(‘« Предыдущая страница’) ?> <?php previous_posts_link(‘Следующая страница »’) ?> <?php else : ?> <h2 class=«center»>Не найдено</h2> <p class=«center»>Извините, но того, что Вы ищите, здесь нет.</p> <?php endif; ?> <!-- подвал --> <?php get_footer(); ?>

Предположим что содержимое файла style.css мы сделаем вот таким:

Итак, после всех этих операций мы увидим что среди выбора шаблонов появился наш шаблон:

1. В папке .../wp-content/themes/ создадим новую папку с темой. Я свою тестовую тему назвал «habr». В результате у меня получилось http://wordpress/wp-content/themes/habr/ 2. Чтобы ВП увидел нашу тему в ней должны присутствовать как минимум 2 файла: /index.php /style.css Получаем: http://wordpress/wp-content/themes/habr/index.php http://wordpress/wp-content/themes/habr/style.css

/* Theme Name: HABR-test Theme URI: http://habr.ru Description: Theme. Version: 1.0 Author: Aesma Author URI: http://aesma.habr.ru Tags:theme */ html { height: 100% } body { height: auto !important; height: 100%; min-height: 100%; }

а в index.php поместим вот это:

habradigest #2 • октябрь 2008

А активировав его увидим примерно вот это:

23


WORDPRESS

<html xmlns=«http://www.w3.org/1999/xhtml»> <head profile=«http://gmpg.org/xfn/11»>

Ответ на вопросы «что это» и «зачем это нужно» вам может дать Гугл. Я не буду тратить время, пойдем дальше. Тип документа и его кодировка: <meta http-equiv=«Content-Type» content=»text/html; charset=UTF-8» />

text/html можно было бы вывести тэгом <?php bloginfo('html_type'); ?>

* С текстовым наполнененем для теста шаблона мне помогли Яндекс. Рефераты

а UTF-8 тэгом <?php bloginfo('charset'); ?>

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

И строка выглядела бы так:

ЧАСТЬ ВТОРАЯ «Сделать чтобы оно работало хорошо»

charset=«<?php bloginfo('charset'); ?>» />

В первой части мы уже создали работоспособный шаблон. Почему же он работает если по сравнению с остальными шаблонами ему не хватает кучи файлов? Объясню по простому: если файл отсутствует — wordpress вставит на его место код используемый по умолчанию. В некоторых случаях это удобно, а в некоторых хочется сделать что-то свое, крутое, нестандартное. Пока особые извращения мы устраивать не станем, а просто аккуратно распределим шаблон по нескольким ключевым файлам. 1. Подключаем «шапку» В папке http://wordpress/wp-content/themes/habr/ создадим файл header.php. В файле index.php менять ничего не нужно, как вы помните мы уже подключили шапку тэгом <?php get_header(); ?>

Ок, файл создали, он подключен, теперь нужно наполнить его. Первые 3 строки я использую во всех темах ничего не меняя: <!DOCTYPE html PUBLIC «-//W3C//DTD XHTML 1.0 Transitional//EN» «http://www.w3.org/TR/xhtml1/DTD/xhtml1 transitional.dtd»>

habradigest #2 • октябрь 2008

<meta http-equiv=«Content-Type» content=«<?php bloginfo('html_ type'); ?>;

Но я думаю гуманнее все-таки использовать статичный вариант. Нам ведь не нужно часто это менять. Идем далее: <meta name=«description» content=«<?php if ( is_single() ) { ?><?php wp_title(''); ?> <?php } ?> <?php if ( is_home() ) { ?><?php bloginfo('description'); ?><?php } ?>»/> <meta name=«keywords» content=«<?php if ( is_single() ) { ?><?php wp_title(''); ?> <?php } ?> <?php if ( is_home() ) { ?><?php bloginfo('name'); ?><?php } ?>» />

Что делает этот код? он выводит на страницах блога мета тэги keywords и description. Код вполне можно не использовать, это для тех кто верит что поисковые системы обращают внимание на эти тэги. Я все-таки использую. Работает он довольно просто: если мы находимся на главной то в тэгах будет заголовок блога, если на странице записи, то там будет заголовок записи. Далее: <title> <?php if ( is_single() ) { ?><?php wp_title(''); ?> - <?php } ?> <?php bloginfo('name'); ?> </title>

Это у нас заголовок документа, именно он показывается у вас на вкладке браузера (или не на вкладке, если у вас неправильный браузер) Далее подключим кое-какие файлы:

24


WORDPRESS

<link rel=«stylesheet» href=«<?php bloginfo('template_url'); ?>/reset. css» type=«text/css» media=«screen»> <!-- обнуляем предустановки браузера --> <link rel=«stylesheet» href=»<?php bloginfo('stylesheet_url'); ?>» type=«text/css» media=»screen» /> <!-- подключем стандартный файл стилей --> <link rel=«alternate» type=«application/rss+xml» title=«<?php bloginfo('name'); ?> RSS Feed» href=«<?php bloginfo('rss2_url'); ?>» /> <!-- показываем что у нашего блога есть RSS 2.0 -->

Как вы заметили — у нас появился новый файл reset. css. Если вкратце, то он помогает нам с решением проблемы кроссбраузерности. Все проблемы он конечно не решит, но жизнь облегчит значительно. Я обычно использую его вот в такой конфигурации: html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td { margin: 0; padding: 0; border: 0; outline: 0; font-size: 100%; vertical-align: baseline; background: transparent; } body { line-height: 1; } ol, ul { list-style: none; } blockquote, q { quotes: none; } blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } /* remember to define focus styles! */ :focus { outline: 0; } /* remember to highlight inserts somehow! */ ins { text-decoration: none; } del { text-decoration: line-through; }

habradigest #2 • октябрь 2008

/* tables still need 'cellspacing=»0»' in the markup */ table { border-collapse: collapse; border-spacing: 0; }

* Страничка автора Далее нам необходим тэг: <?php wp_head(); ?>

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

Все, файл пока закончен. Вот что должно получится: <!DOCTYPE html PUBLIC «-//W3C//DTD XHTML 1.0 Transitional// EN» «http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd»> <html xmlns=«http://www.w3.org/1999/xhtml»> <head profile=«http://gmpg.org/xfn/11»> <meta http-equiv=«Content-Type» content=»text/html; charset=UTF-8» /> <meta name=«description» content=«<?php if ( is_single() ) { ?><?php wp_title(''); ?> <?php } ?> <?php if ( is_home() ) { ?><?php bloginfo('description'); ?><?php } ?>»/> <meta name=«keywords» content=«<?php if ( is_single() ) { ?><?php wp_title(''); ?> <?php } ?> <?php if ( is_home() ) { ?><?php bloginfo('name'); ?><?php } ?>» /> <title><?php if ( is_single() ) { ?><?php wp_title(''); ?> - <?php } ?> <?php bloginfo('name'); ?></title> <link rel=«stylesheet» href=«<?php bloginfo('template_url'); ?>/reset. css» type=«text/css» media=«screen»> <!-- обнуляем предустановки браузера --> <link rel=«stylesheet» href=«<?php bloginfo('stylesheet_url'); ?>» type=«text/css» media=«screen» /> <!-- подключем стандартный файл стилей --> <link rel=«alternate» type=«application/rss+xml» title=«<?php bloginfo('name'); ?> RSS Feed» href=»<?php bloginfo('rss2_url'); ?>» /> <!-- показываем что у нашего блога есть RSS 2.0 --> <?php wp_head(); ?> </head> <body>

25


WORDPRESS

2. Подключаем «подвал» По аналогии с шапкой в папке http://wordpress/wp-content/themes/habr/ создадим файл footer.php. В файле index.php менять ничего не нужно, как вы помните мы уже подключили подвал тэгом <?php get_footer(); ?>

С footer.php мы сейчас поступим значительно проще чем с header.php. Добавим в него всего 2 строки: </body> </html>

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

<table id=”main”> <tr> <td id=”head”></td> <td id=”rss”></td> </tr>

и сохраняем файл. 2. Открываем файл footer.php и прямо перед закрытием тэга </body> дописываем: <tr> <td id=”footer”></td> <td id=”counters”></td> </tr> </table>

и сохраняем файл. 3. В папке http://wordpress/wp-content/themes/habr/ создадим файл sidebar.php Пусть он пока будет пуст, мы вернемся к нему позже. 4. Открываем файл index.php и после тэга <?php get_ header(); ?> добавим следущий код:

ЧАСТЬ ТРЕТЬЯ «Добавим каркас и навигацию»

<tr> <td id=»content»>

Прежде чем продолжить напомню что незнакомые вам тэги(переменные) вы можете посмотреть в Кодексе ВордПресс и вот в этой шпаргалке.

Далее нам нужно найти тэг <?php endif; ?> и вставить после него следющий код:

Итак, у нас есть заготовка для шаблона. Теперь можно показать что я хочу дальше с ней сделать (рисунок). Я поклонник таблично-дивной верстки поэтому холиваров в комментариях надеюсь не будет. Визуально все будет довольно просто, вот как к примеру у Ильи Бирмана. Цель статьи ведь не обучение фотошопу, а помошь в ориентировании по коду. Начнем верстать: 1. Открываем файл header.php и сразу после открытия тэга <body> дописываем:

</td> <td id=”sidebar”> <?php get_sidebar(); ?> </td> </tr>

и сохранить файл. Как вы можете заметить последним действием мы завершили построение табличной сетки, а так же подключили файл боковой колонки, который создали ранее в пункте 3. Если открыть наше творение в браузере то мы увидим примерно следующее (см. рисунок). Почему все так страшно выглядит, и нет бокового меню? Потому что стили мы с вами еще не написали, а файл sidebar.php как вы помните у нас сейчас пустой. Давайте заполним пробелы. 5. Откроем файл style.css и в конец файла добавим вот такой код: #main { height: 100%; } #content, #footer, #head { width: 70%; height: 150px; }

habradigest #2 • октябрь 2008

26


WORDPRESS

</div> </td> <td id=”rss”> <ul> <li><a id=”blogrss” href=”<?php bloginfo('rss2_url'); ?>”>Мой RSS поток</a></li> <li><a id=”yandexrss” href=”http://lenta.yandex.ru/settings.xml?name=feed&url=<?php bloginfo('rss2_url'); ?>”> Читать в Яндекс.Ленте</a></li> <li><a id=”yahoorss” href=”http://add.my.yahoo.com/rss?url=<?php bloginfo('rss2_url'); ?>”> Читать через Yahoo</a></li> <li><a id=”googlerss” href=”http://www.google.com/ig/add?feedurl=<?php bloginfo('rss2_ url'); ?>”> Читать через Google</a></li> </ul> </td> </tr>

Сохраняем файл. Если мои расчеты верны, то больше нам его редактировать не потребуется. #rss, #sidebar, #counters{ width: 30%; height: 100px; }

Сохраним. В браузере уже заметны изменения:

7. В style.css допишем следующий код: #head, #rss, #counters, #footer { vertical-align:middle; } #head { padding-left:70px; }

Сохраним. Проверим, шапка должна выглядеть примерно так:

8. Вернемся к файлу sidebar.php. Это наша боковая колонка, в ней мы разместим большую часть навигации по блогу. Сейчас мы добавим в нее несколько стандартных блоков, а со временем вы уже сами определить что вам там нужно, а что нет. Итак, поместим в файл sidebar.php следующий код:

6. Опять идем в header.php и сразу после открытия тэга <body> заменяем все вот на это:

<div class=»sideblock»> <h3>Страницы</h3> <!-- статичные страницы --> <ul> <?php wp_list_pages('title_li='); ?> </ul> </div>

<table id=”main”> <tr> <td id=”head”> <div id=”header”> <h1><a href=”<?php bloginfo('url'); ?>”><?php bloginfo('name'); ?></ a></h1> <?php bloginfo('description'); ?>

<div class=»sideblock»> <h3>Категории</h3> <!-- категории --> <ul> <?php wp_list_categories('title_li='); ?> </ul> </div>

Ок. Украшение отложим на потом, а сейчас продолжим копаться в коде.

habradigest #2 • октябрь 2008

27


WORDPRESS

<div class=”sideblock”> <h3>Архив</h3> <!-- архив по месяцам --> <ul> <?php wp_get_archives('type=monthly'); ?> </ul> </div>

ЧАСТЬ ЧЕТВЕРТАЯ «Наводим порядок» Прежде чем продолжить я решил немного поработать над css, а то с минимум стилей иметь дело было как то неудобно. Я зашел на Typechart.com и выбрав несколько подходящих мне вариантов собрал из них следующее:

<div class=”sideblock”> <h3>Календарь</h3> <!-- календарь --> <?php get_calendar(daylength); ?> </div> <div class=”sideblock”> <h3>Блогролл</h3> <!-- ссылки --> <ul> <?php get_links('-1', '<li>', '</li>', '<br />', FALSE, 'rand', FALSE, FALSE, 10, TRUE); ?> </ul> </div>

а в файл style.css добавим пункт .sideblock { padding-bottom:20px; }

Сохраним изменения и полюбуемся на результат в браузере:

Чтобы получить такой же результат вам надо добавить в файл style.css вот это: a{ color:#79B1D4; } input#url, input#email, input#author { width:200px; font-size: 11px; border: 1px solid #DDDDDD; padding:3px 3px 3px 3px; } #header { font-size: 10px; color:#8F8F8F; } #header h1 { font-size: 36px; font-weight:normal; font-style: normal; text-transform: normal; letter-spacing: -2px; line-height: 1.2em; }

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

habradigest #2 • октябрь 2008

#header h1 a { color:#79B1D4; text-decoration:none; } #content { font-size: 13px; padding: 0 40px 0 40px; }

28


WORDPRESS

.postinfo { font-size:11px; border-top: 1px solid #DFDFDF; padding:10px 0; margin: 10px 0px 20px 0px; } #content h2 { font-size:24px; text-transform:none; font-weight:normal; letter-spacing: -1px; padding-bottom: 3px; margin-bottom: 10px; } #content h2 a { color:#000000; text-decoration:none; }

#sidebar ul { list-style-type:square; list-style-position:inside; } #sidebar ul li { line-height:18px; } #sidebar ul li a { font-size:12px; text-transform:none; font-weight:normal; text-decoration:none; }

#content p { line-height:18px; padding-bottom:10px; }

.postmetadata { font-size:10px; background-color:#EFEFEF; border-top: 1px solid #DFDFDF; border-bottom: 1px solid #DFDFDF; padding: 10px 20px 10px 20px; margin-bottom:10px; color: #5F5F5F; }

#rss ul { list-style-type:square; list-style-position:inside; }

.comment { font-size:11px; padding-bottom: 10px; }

#rss ul li { line-height:18px; }

.commenttext { background-color:#FAFAFA; font-size:12px; border-top: 1px solid #DFDFDF; border-bottom: 1px solid #DFDFDF; padding: 10px 10px 0px 10px; margin-top:10px; }

#rss ul li a { color:#BF816B; font-size:11px; text-transform:none; font-weight:normal; text-decoration:none; padding-bottom: 2px; border-bottom: 1px dashed #CCCCCC; } #sidebar { padding-right:40px; } #sidebar h3 { font-size: 11px; font-weight:bold; text-transform: uppercase; padding-bottom:10px; } #searchform input { width:245px; font-size: 11px; border: 1px solid #DDDDDD; padding:3px 3px 3px 3px; }

habradigest #2 • октябрь 2008

#postcomment { font-size: 11px; font-weight:bold; text-transform: uppercase; padding:10px 0; } textarea#comment { width:600px; font-size: 12px; margin:5px 0px 5px 0px; font-size: 11px; border: 1px solid #DDDDDD; padding:3px 3px 3px 3px; } #list { }

text-align: center;

/* calendar */

29


WORDPRESS

#wp-calendar { width: 245px; font-size: 11px; color: #232323; } #wp-calendar #next a { padding-right: 10px; text-align: right; } #wp-calendar #prev a { padding-left: 10px; text-align: left; } #wp-calendar a { display: block; text-decoration: none; font-weight:bold; } #wp-calendar a:hover { color: #353535; } #wp-calendar caption { color: #232323; font-size: 11px; text-align: center; font-weight:bold; padding: 10px 0px 10px 0px; } #wp-calendar td { width: 35px; color: #232323; padding: 4px 0; text-align: center; border-top: 1px solid #ddd; } #wp-calendar td#prev, #wp-calendar td#next { width:105px; } #wp-calendar td.pad:hover { text-align: center; color: #232323; } #wp-calendar #today { color: #232323; border-left: 1px solid #ddd; border-right: 1px solid #ddd; background: #ddd; font-weight: bolder; } #wp-calendar td:hover{ color: black; }

habradigest #2 • октябрь 2008

#wp-calendar th { padding: 4px 0; font-style: normal; text-transform: capitalize; text-align: center; border-top: 1px solid #ddd; }

Получилось? Тогда приступим к доработке шаблона. 1. В папке http://wordpress/wp-content/themes/habr/ создадим файл single.php это шаблон показывающий как у нас будет выглядеть страница отдельного поста. При генерации шаблона код из этого файла заменяет код который мы использовали в файле index.php, поэтому они во многом похожи. <?php get_header(); ?> <tr> <td id=”content”> <?php if (have_posts()) : while (have_posts()) : the_post(); ?> <h2><?php the_title(); ?></h2> <?php the_content('Дальше »'); ?> <p class=”postmetadata”> Эта запись опубликована <?php the_time('l, F jS, Y') ?> в <?php the_time() ?> Рубрики: <?php the_category(', ') ?>. Подпишитесь на <?php comments_rss_link('RSS 2.0 ленту'); ?> комментариев. <?php if (('open' == $post-> comment_status) && ('open' == $post>ping_status)) { // Both Comments and Pings are open ?> Вы можете <a href=”#respond”>оставить комментарий</a> или <a href=”<?php trackback_url(true); ?>” rel=”trackback”>трекбек</a> со своего сайта. <?php } elseif (!('open' == $post-> comment_status) && ('open' == $post->ping_status)) { // Only Pings are Open ?> Комментирование запрещено, но Вы можете поставить <a href=”<?php trackback_url(true); ?>“ rel=”trackback”>трекбек</a> со своего сайта. <?php } elseif (('open' == $post-> comment_status) && !('open' == $post->ping_status)) { // Comments are open, Pings are not ?> Вы можете оставить комментарий. Пинг запрещен. <?php } elseif (!('open' == $post-> comment_status) && !('open' == $post->ping_status)) { // Neither Comments, nor Pings are open ?> Комментироваие и пингование закрыты. <?php } edit_post_link(‘Редактировать.’,’’,’’); ?> </p>

30


WORDPRESS

<?php comments_template(); ?>

</div>

<?php endwhile; ?>

<?php endforeach; ?>

<?php else : ?>

<?php else : // If there are no comments yet ?> <p><?php _e(‘Еще нет комментариев.’); ?></p> <?php endif; ?>

<h2 class=”center”>Не найдено</h2> <p class=”center”>Извините, но того, что Вы ищите, здесь нет.</p> <center><?php include (TEMPLATEPATH . «/searchform.php»); ?></ center> <?php endif; ?> </td> <td id=”sidebar”> <?php get_sidebar(); ?> </td> </tr> <?php get_footer(); ?>

Как видите мы убрали листинг страниц, добавили вывод тех. информации о посте, и тэгом <?php comments_template(); ?> Показали в каком месте страницы будут выводиться комментарии и форма комментирования. Теперь страничка поста у нас выглядит вот так:

<?php if ( comments_open() ) : ?> <div id=”postcomment”>Оставьте комментарий</div> <?php if ( get_option('comment_registration') && !$user_ID ) : ?> <p>Вы должны <a href=”<?php echo get_option('siteurl'); ?>/wp-login.php?redirect_ to=<?php the_permalink(); ?>”>залогиниться</a> для комментирования.</p> <?php else : ?> <form action=”<?php echo get_option('siteurl'); ?>/wp-comments-post. php” method=”post” id=”commentform”> <?php if ( $user_ID ) : ?> <p>Вы вошли как <a href=”<?php echo get_option('siteurl'); ?>/wpadmin/profile.php”> <?php echo $user_identity; ?></a>. <a href=”<?php echo get_option('siteurl'); ?>/wp-login. php?action=logout” title=”<?php _e('Выйти из аккаунта') ?>”>Выйти »</a></p> <?php else : ?> <p><input type=”text” name=”author” id=”author” value=”<?php echo $comment_author; ?>” size=”22” tabindex=”1” /> <label for=”author”><small>Имя <?php if ($req) _e('(обязательно)'); ?></small> </label></p>

2. Прикрутим комментарии. В папке http://wordpress/wp-content/themes/habr/ создадим файл comments.php и поместим в него следующий код: <?php if ( !empty($post->post_password) && $_COOKIE['wppostpass_' . COOKIEHASH] != $post->post_password) : ?> <p><?php _e('Enter your password to view comments.'); ?></p> <?php return; endif; ?> <?php if ( $comments ) : ?> <?php foreach ($comments as $comment) : ?> <div class=”comment”> <?php comment_type(__('Комментарий'), __('Trackback'), __ ('Pingback')); ?> от <b><?php comment_author_link() ?></b> | Дата: <?php comment_ date('F jS, Y'); ?> в <?php comment_time() ?> <?php edit_comment_link('Править','',''); ?> <div class=”commenttext”> <?php comment_text() ?> </div>

habradigest #2 • октябрь 2008

<p><input type=”text” name=”email” id=”email” value=”<?php echo $comment_author_email; ?>” size=”22” tabindex=”2” /> <label for=”email”><small>E-mail <small>(не публикуется)</small> <?php if ($req) _e('(обязательно)'); ?></small></label></p> <p><input type=”text” name=»url» id=”url” value=”<?php echo $comment_author_url; ?>” size=”22” tabindex=”3” /> <label for=»url»><small>Сайт </small></label></p> <?php endif; ?> <p><textarea name=”comment” id=”comment” rows=”10” tabindex=”4”></textarea></p> <p> <input name=”submit” type=”submit” id=”submit” tabindex=”5” value=”Комментировать” /> <input type=”hidden” name=”comment_post_ID” value=”<?php echo $id; ?>” /> </p> <?php do_action('comment_form', $post->ID); ?> </form>

31


WORDPRESS

<?php endif; // If registration required and not logged in ?> <?php else : // Comments are closed ?> <p><?php _e(‘Комментарии закрыты.’); ?></p> <?php endif; ?>

Думаю все понятно что к чему, русские слова показывают что где выводится. Я обычно в этом коде не копаюсь и использую один и тот же вариант в разных темах. Вот так теперь дожны выглядеть комментарии:

Если внимательно присмотреться вы увидите что код вам знаком. Для шаблона страницы мы использовали тот же код что и для шаблона постов, просто удалили из него объекты выводящие комментарии и информацию о записи. Теперь страницы у нас будут выглядеть вот так:

4. Форма поиска. Откроем файл sidebar.php и добавим в начало файла следующий код: Продолжаем. 3. Создадим шаблон для статичных страниц. В папке http://wordpress/wp-content/themes/habr/ создадим файл page.php и поместим в него следующий код: <?php get_header(); ?> <tr> <td id=»content»> <?php if (have_posts()) : while (have_posts()) : the_post(); ?> <h2><?php the_title(); ?></h2> <?php the_content('Дальше »'); ?> <?php endwhile; ?> <?php else : ?> <h2 class=”center”>Не найдено</h2> <p class=”center”>Извините, но того, что Вы ищите, здесь нет.</p> <center><?php include (TEMPLATEPATH . «/searchform.php»); ?></ center> <?php endif; ?> </td> <td id=”sidebar”> <?php get_sidebar(); ?> </td> </tr> <?php get_footer(); ?>

habradigest #2 • октябрь 2008

<div class=”sideblock”> <!-- форма поиска --> <form method=”get” id=”searchform” action=”<?php bloginfo('home'); ?>/”> <input type=”text” name=”s” id=”s” value=”Искать по блогу...” onfocus=”if (this.value=='Искать по блогу...') this.value='';”/> </form> </div>

Вот и все, в сайдбаре должна появится форма поиска. 5. Добавим информации о постах. Откроем файл index. php и чтоб не мучать себя поиском по коду просто заменим все содержимое вот на этот код: <!-- шапка --> <?php get_header(); ?> <tr> <td id=”content”> <!-- начало ленты постов --> <?php if (have_posts()) : while (have_posts()) : the_post(); ?> <div class=”post”> <!-- заголовок поста --> <h2><a href=”<?php the_permalink() ?>” rel=”bookmark” title=”<?php the_title(); ?>”><?php the_title(); ?></a></h2> <!-- текст поста --> <?php the_content('Дальше »'); ?> <div class=”postinfo”>

32


WORDPRESS

Дата: <?php the_time('d M') ?> | Категория: <?php the_category(', ') ?> | <?php edit_post_link('Редактировать', '', ' | '); ?> <?php comments_popup_link(‘Нет комментариев’, ‘1 Комментарий’, ‘Комментарии:%’); ?> </div> </div> <?php endwhile; ?> <!-- конец ленты постов --> <div id=»list»> <?php next_posts_link('Предыдущая страница') ?> <?php previous_posts_link('Следующая страница') ?> </div> <?php else : ?> <h2 class=»center»>Не найдено</h2> <p class=”center”>Извините, но того, что Вы ищите, здесь нет.</p> <center><?php include (TEMPLATEPATH . «/searchform.php»); ?></ center> <?php endif; ?> </td> <td id=»sidebar»> <!-- боковая колонка --> <?php get_sidebar(); ?> </td> </tr> <!-- подвал --> <?php get_footer(); ?>

Все. Должно получится вот так как на рисунке.

<!-- вместо этого комментария вставьте код вашего счетчика -->

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

</td> </tr> </table> </body> </html>

ЧАСТЬ ПЯТАЯ «Последняя» В прошлый раз мы закончили на том что тема наша почти готова, осталось доделать футер. Так давайте же уже доделаем его: Откройте файл footer.php и замените его содержимое на вот такой код:

а в файл style.css добавьте вот такой пункт: #copyleft { font-size: 11px; margin: 0 40px; line-height: 30px; border-top: 1px solid #ddd; }

Должно получиться как-то так:

<tr> <td id=”footer”> <div id=”copyleft”> © 2008 «<?php bloginfo('name'); ?>». Работает на <a href=”http://wordpress.org/”>WordPress</a> </div> </td> <td id=”counters”>

habradigest #2 • октябрь 2008

33


WORDPRESS

ВСЕ. Шаблон закончен. Однако, есть еще несколько вещей с которыми зачастую возникают затруднения:

дите код или «шаблонный тэг» с помощью которого это делается. Пример, плагин WP-PageNavi:

1. Нагрузка на сервер.

Заменяет стандартное перелистывание страничек вида:

Если у вас слабый хостинг, вам следует заняться оптимизацией шаблона, ведь каждый «шаблонный тэг» кушает ресурсы сервера. Чем больше данных вы сможете перевести в статический html-код тем легче серверу.

«назад | вперед » На подобную навигацию:

К примеру <?php bloginfo('name'); ?> Это название вашего блога. Не думаю что вы часто его меняете. Значит его можно написать просто вручную. или вот <?php bloginfo('url'); ?> Адрес вашего блога. Тоже редкоизменяемая вещь. Просмотрите весь код, от хедера до футера, не забудьте файлы постов и страниц. Посмотрите, возможно в вашей теме можно вывести просто кодом: пути к css файлам и подключенным скриптам тэг title в заголовке страницы список страниц title у ссылок что-то еще?... 2. Отобразить какую-то определенную информацию. Возможнно ее можно вывести каким либо из стандартных тэгов? Изучите http://codex.wordpress.org/Template_ Tags (англ.) и вот эту шпаргалку. Возможно вы найдете там то что вам поможет. Если не нашли — вам нужны плагины. 3. Как подключить плагины к шаблону? Если вы хотите использовать в вашем ша\блоне плагины требующие замены или добавления кода в шаблоне — внимательно изучите документацию плагина и най-

habradigest #2 • октябрь 2008

Для его установки нам потребовалось бы заменить в теме вот эти тэги: <?php next_posts_link('« Предыдущая страница') ?> <?php previous_posts_link('Следующая страница »') ?>

на вот такой тэг: <?php if(function_exists(‘wp_pagenavi’)) { wp_pagenavi(); } ?>

4. Оптимизация под поисковики и планировка шаблона. Так уж получилось, что несмотря на то что я дизайнерлюбитель и верстальщик-любитель, по профессии я кто-то там из области seo. Или как принято говорит на Хабре — «долбаный SEOшник». Давным давно, в 2007 году я написал статью о SEO/SMO оптимизации WordPress. Сюда переносить ее не буду, ибо она несколько не в том формате. Просто дам ссылку — http:// www.aesma.ru/2007/09/08/seosmo-optimizatsiya-bloga-nabaze-wordpress.html. Думаю зеленые циферки именуемые здесь «кармой» выдержат несколько минусов. Все что там написано можно не читать, в контексте нашей темы там интересны только четвертый и, может быть, пятый и седьмой пункты. На этом моя статья из пяти частей о создании шаблона для WP закончена. Удачного вам кодинга.

34


WORDPRESS

Ускоряем wordpress voicer

Привет. Думаю, среди читателей хабра найдется немало тех, кто имеет stand-alone blog на движке wordpress. Так вот, для вас, дорогие мои, у меня есть две новости, как водится, плохая и хорошая Плохая состоит в том, что wordpress — довольно-таки тормознутая штука. Виноваты в этом в основном криворукие производители тем и, особенно, криворукие производители плагинов. Особенно кривой плагин, на мой вкус, wp-ajax-edit-comments, который является образцом быдлокодинга. Хорошая — в том, что это можно поправить.

Теория Сначала немного теории. Уже довольно давно умные люди из компании Yahoo провели исследования на тему «как же нам ускорить наши сайты». И выяснили, что на скорость сайта с точки зрения пользователя в основном влияет оптимизация front-end'a, а не server-side. Подробнее об этом можно почитать на сайте webo.in на русском и на сайте yahoo на английском, я же просто опишу несколько простых шагов, которые позволят существенно ускорить скорость работы своего блога. Предупреждение: хотя я и старался максимально упростить текст, сведя его к набору инструкций, все же большинство шагов можно выполнить только имея опыт разработки веб-сайтов, так что, если вы не программист, то лучше попросите знакомого программиста выполнить эти шаги за вас. Да, и на всякий случай, не забудьте забэкапиться :) Перед тем, как мы перейдем к практике, я напомню нашу основную цель: блог на движке wordpress должен работать с точно тем же функционалом, что и раньше, но, с точки зрения пользователя, работать быстрее Итак, погнали.

Практика, оптимизируем тему Да, открытость платформы Wordpress — это очевидное благо. Я серьезно. Множество прекрасных дизайнеров, верстальщиков, программистов с горящими от энтузиазма глазами вдохновенно, вдумчиво создают темы и плагины для всех, для всего человечества, не требуя ничего взамен. Это действительно прекрасно. К сожалению, не обошлось и без ложки дегтя — далеко не все верстальщики и программисты одинаково опытны и талантливы и далеко не все они в достаточной степени усердны и ответственны, чтобы на общественно-

habradigest #2 • октябрь 2008

добровольных началах вылизать тему, которую они предоставляют. Еще большее сожаление прогрессивно мыслящей части человечества вызывает тот факт, что уебанов больше, чем титанов духа, и в связи с этим сеть полна бесплатного говна. Таким образом, нам надо исправить недостатки, которые (возможно) присущи вашей любимой теме от рождения и поправить ее код. Итак, для того, чтобы тема стала работать быстрее, надо сделать следующее: Если тема сверстана на таблицах, переверстать ее на дивы. Я не буду касаться давнего спора «как верстать — дивами и таблицами», замечу только, что точки зрения поставленной перед нами цели (быстро работающий с точки зрения пользователя блог) таблицы проигрывают дивам — потому что таблица отрисовывается браузером только после того, как будет полностью загружена, тогда как дивы отрисовываются сразу, как только браузер получит их с сервера. А значит, если страница сверстана в дивах, пользователь быстрее увидит контент сайта, что нам и нужно, не так ли? Кроме того, как указывает в комментариях len : верстка div-ами вместе с разбиением странички на файлы типа left-sidebar, right-sidebar, header, footer etc. позволит быстрее и проще поменять какой-нибудь небольшой кусочек кода прямо из панели управления движком (Дизайн -> Редактор тем), чем попытки из этой же панели управления поменять кусочек своей табличной верстки, в которой можно потеряться за забором из tr и td. 1. Вы будет смеяться, но нужно убрать все стилевые правила во внешние файлы. И JavaScript — тоже. Это настолько очевидно, что я даже не буду пояснять, зачем это нужно.

35


WORDPRESS

2. Внешний стилевой файл прописываем в блоке head, а JavaScript файл подключаем как можно ближе к закрывающему тэгу «body». И все скрипты аналитиков тоже располагаем пониже.

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

Сжимаем js и css. Для этого мы будем использовать yuicomressor. Делается это примерно так: качаем последний стабильный релиз YUICompressor'a с официального сайта, устанавливаем, если еще не установлено JRE и выполняем на css/js файл команду следующего вида:

При этом сами картинки не изменятся и все так же будут радовать глаз пользователей.

java -jar /path/to/yuicompressor-*.*.*.jar -o “output_filename” src_file

Итак, для оптимизации всего png картинок мы будем использовать pngcrush. Я затрудняюсь ответить, как ее инсталлировать на windows или linux, но я на своем mac'e без проблем установил эту утилиту из портов.

можно использовать флаг -type, который указывает, какой тип файла (css или js). Если флаг не указан, то тип файла определяется по расширению. Можете использовать тему моего блога как пример. Ниже я привел пример shell-скрипта, который можно натравить на директорию, в которой расположена тема, и он все сделает за вас.

Уменьшаем количество файлов Так как мы можем серьезно ускорить скорость загрузки файлов, уменьшив число этих файлов (удивительно, правда? :), то разумнее всего будет слить все css-файлы и js-файлы в один. Если эти файлы размером больше ~70 килобайт, то лучше разбить их на два куска. Если нужно уменьшить количество картинок, используя технику css спрайтов и технику image map. Тут надо отдельно заметить, что у многих (практически у всех) плагинов есть совершенно идиотская особенность — прописывать свои js и css файлы. Идиотизм заключается в том, что очень часто разные плагины используют одну и ту же библиотеку, и подключают ее по нескольку раз. И пользователь, просматривающий ваш блог, вынужден по два-три раза грузить, к примеру, prototype или jquery. Лишние ~30-160+ KB. Неслабо, прадва? Тех. заметка: при этом совершенно непонятно, что мешало создателям wordpress сделать контроль надо всеми ресурсами, что прописывают плагины, как сделано, к примеру, в RichFaces. К примеру, если один плагин, которому нужен jQuery, прописывает тэг script с jQuery и рапортует — «Вот мол, подгрузил, все, кому надо, могут использовать», а другой, которому тоже нужен jQuery уже знает об этом и не подгружает свой вариант. Лечится это так — все скрипты, что подгружают плагины слейте с теми, что написали сами и воткните в футер, а плагинам запретите их подгружать. Тоже самое сделайте с css, только воткните в header.

Информацию об этих утилитах и команду я позаимствовал у Дмитрия Ищенко. Надеюсь, он не в обиде :)

Чтобы сжать png-файлы без потери качества, используйте следующую команду: pngcrush -rem alla -reduce -brute image.png result.png

Для сжатия jp(e)g-файлов используйте jpegtran, которая входит в пакет libjpg, который я также установил из портов. Команда для сжатия jp(e)g-файлов без потери качества: jpegtran -copy none -optimize -perfect src.jpg dest.jpg

Я тут набросал shell-скрипт, который рекурсивно пройдет по директории и оптимизирует все png/jp(e)g картинки в ней: for file in `find . -iname “*.jpg” -or -iname “*.png” -or -iname “*.jpeg”`;do ext=${file##*.} if [ -n “$ext” ]; then if [ “$ext” = “jpg” ]; then echo “optimizing ${file} as jpeg file with jpegtran” jpegtran -copy none -optimize -perfect -outfile temp_abracadabra_ filename.jpg $file mv -f temp_abracadabra_filename.jpg $file; fi if [ “$ext” = “jpeg” ]; then echo “optimizing ${file} as jpeg file with jpegtran” jpegtran -copy none -optimize -perfect -outfile temp_abracadabra_ filename.jpeg $file mv -f temp_abracadabra_filename.jpeg $file; fi if [ “$ext” = “png” ]; then echo “optimizing ${file} as png file with pngcrush” pngcrush -rem alla -reduce -brute “$file” temp_abracadabra_ filename.png; mv -f temp_abracadabra_filename.png $file; fi fi done;

Оптимизируем графику Картинки в png и jpg форматах довольно часто неоптимизированы и хранят много лишней информации. Было бы неплохо избавиться от этой лишней информа-

habradigest #2 • октябрь 2008

Просто выполните его в директории uploads что в папке wp-content и все будет хорошо. Можно даже повесить его на cron-job и больше не париться на этот счет

36


WORDPRESS

— все вновь прибывшие файлы будут оптимизироваться.

Найдите файл .htaccess в корневой директории установки wordpress и добавьте в конец следующее:

Разумеется, надо оптимизировать графику, используемую в теме, так что вот еще один shell-скрипт, который рекурсивно пройдет по директории, сожмет css, js файлы и оптимизирует jp(e)g/png файлы. На всякий случай перед его использованием не забудьте забэкапиться:

<IfModule mod_deflate.c> AddOutputFilterByType DEFLATE text/html text/plain text/xml SetOutputFilter DEFLATE BrowserMatch ^Mozilla/4 gzip-only-text/html BrowserMatch ^Mozilla/4\.0[678] no-gzip BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html SetEnvIfNoCase Request_URI \.(?:gif|png)$ no-gzip dont-vary Header append Vary User-Agent env=!dont-vary </IfModule>

for file in `find . -iname «*.jpg» -or -iname «*.jpeg» -or -iname «*.png» -or -iname «*.js» -or -iname «*.css» `;do ext=${file##*.} if [ -n “$ext” ]; then if [ “$ext” = “css” ]; then echo “compressing ${file} as css file with yui compressor” java -jar /opt/yuicompressor/yuicompressor-2.3.5.jar --type css -o “temp_abracadabra_filename.css” $file mv -f temp_abracadabra_filename.css $file; fi if [ “$ext” = “js” ]; then echo “compressing ${file} as js file with yui compressor” java -jar /opt/yuicompressor/yuicompressor-2.3.5.jar --type js -o “temp_abracadabra_filename.js” $file mv -f temp_abracadabra_filename.js $file; fi if [ “$ext” = “jpg” ]; then echo “optimizing ${file} as jpeg file with jpegtran” jpegtran -copy none -optimize -perfect -outfile temp_abracadabra_ filename.jpg $file mv -f temp_abracadabra_filename.jpg $file; fi if [ “$ext” = “jpeg” ]; then echo “optimizing ${file} as jpeg file with jpegtran” jpegtran -copy none -optimize -perfect -outfile temp_abracadabra_ filename.jpeg $file mv -f temp_abracadabra_filename.jpeg $file; fi if [ “$ext” = “png” ]; then echo “optimizing ${file} as png file with pngcrush” pngcrush -rem alla -reduce -brute “$file” temp_abracadabra_filename.png; mv -f temp_abracadabra_filename.png $file; fi fi done;

Используйте меньше dns-lookups. Не выкладывайте картинки, src которых указывает на другой ресурс, лучше загрузите их к себе и пропишите ссылку на автора. Работать будет быстрее.

Сжатие Все современные браузеры поддерживают сжатие, так что можно существенно уменьшить размер отдаваемых файлов (а значит, и время их загрузки) при помощи mod_deflate (для Apache 2.2 для Apache 1.3 надо использовать mod_gzip). Так что включите mod_deflate. Если же вы используете Apache 1.3, ниже приведенный код вам не не нужен, вам поможет статья «mod_gzip — сжатие html страниц 'на лету'» на сайте webo.in.

habradigest #2 • октябрь 2008

Таким способом можно добиться уменьшения js/css/ html файлов на 70-80%, и примерно 10% уменьшения jpeg-файлов, что значительно ускоряет загрузку сайта. Следует, правда, помнить, что использования mod_deflate увеличивает нагрузку на сервер, так как ему нужно сжать файлы перед тем, как отдать их, так что стоит проконтролировать, что использование mod_ deflate не создает чрезмерной нагрузки на сервер.

Кеширование Ну и последнее — для того, чтобы ускорить серфинг по вашему сайта нужно врубить кеширование. Это не ускорит загрузку сайта у пользователя, который первый раз пришел на ваш сайт, но положит все внешние js, css файлы, картинки к нему в кеш, и в следующий раз, когда он будет бродить по вашему сайту, ресурсы будут грузиться не с сервера, а из кеша, что значительно ускорит скорость работы вашего сайта с _точки зрения пользователя_, а также значительно снизит нагрузку на ваш сервер. Помните, что закешированные у пользователя файлы берутся из кеша браузера, поэтому, если вы внесете изменения в файл, скажем, стилей, пользователь, закешировавший style.css, не увидит их. Так что лучше включать кеширование после того, как вы доработали тему. Опять же, добавьте следующие строчки в конец файла .htaccess и не забудьте включить mod_headers и mod_ expires (или хотя бы один из них): # используем mod_expires <IfModule mod_expires.c> ExpiresActive On ExpiresDefault A86400 ExpiresByType image/x-icon A2592000 ExpiresByType application/x-javascript A2592000 ExpiresByType text/css A2592000 ExpiresByType image/gif A604800 ExpiresByType image/png A604800 ExpiresByType image/jpeg A604800 ExpiresByType text/plain A604800 ExpiresByType application/x-shockwave-flash A604800 ExpiresByType video/x-flv A604800 ExpiresByType application/pdf A604800 ExpiresByType text/html A900 </IfModule> # используем mod_header

37


WORDPRESS

<IfModule mod_header.c> # 3 Month <FilesMatch «\.(flv|gif|jpg|jpeg|png|ico|swf)$»> Header set Cache-Control «max-age=7257600» </FilesMatch> # 1 Week <FilesMatch «\.(js|css|pdf|txt)$»> Header set Cache-Control «max-age=604800» </FilesMatch> # 10 Minutes <FilesMatch «\.(html|htm)$»> Header set Cache-Control «max-age=600» </FilesMatch> # NONE <FilesMatch «\.(pl|php|cgi|spl)$»> Header unset Cache-Control Header unset Expires Header unset Last-Modified FileETag None Header unset Pragma </FilesMatch> </IfModule>

Кеширование на стороне сервера Осторожно: этот пункт выполняйте с осторожностью (или, если вы не уверены в том, что делаете, вообще его пропустите), у многих использование перечисленых ниже плагинов вызывает проблемы.

habradigest #2 • октябрь 2008

Есть несколько плагинов к wordpress'у, которые позволяют кешировать файлы на стороне сервера. Работают они по такому принципу: как только какая-то из ваших страниц запрашвается на сервере, она динамически строится и создается html-файл со всеми данными, который ложится в кеш. Как только приходит запрос на эту страницы, пользователю отдается html файл вместо того, чтобы динамически строить страничку, что значительно снижает нагрузку на сервер (теперь ведь не прогоняются php-скрипты и не нагружается база данных). К сожалению, все эти плагины работают как-то очень странно, во всяком случае, у меня не работали должным образом ни wp-cache, ни работающий на его основе wp-super-cache. Зато работает 1 Blog Cacher, правда, у него достаточно сложная настройка. Надеюсь, вы разберетесь. Я использую его.

Итого После того, как я провел над своим блогом ряд этих нехитрых действий, блог стал загружаться на 80% быстрее. Теперь perfomance meter моего сайта с точки зрения плагина к firebug'у YSlow равен B(85), был F(33). Думаю, неплохой результат. Кросс-пост в моем блоге.

38


WEB-РАЗРАБОТКА

CSS Font-Size: em vs. px vs. pt vs. percent XaocCPS (перевод)

Одним из наиболее запутанных аспектов CSS является применение font-size атрибута для масштабирования текста. Используя CSS, вы можете изменить размер текста в браузере с помощью четырех разных единиц измерения. Какая из этих четырех единиц лучше всего подходит для веб? Это вопрос, который породил разнообразные дискуссии и критику. Поиск окончательного ответа затруднен, поскольку вопрос сам по себе сложный.

Знакомьтесь — единицы 1. «Ems» (em): «em» — это масштабируемая единица, которая используется в веб-документах. «em» равна текущему font-size, например, если font-size в документе 12pt, 1em равен 12pt. «em» масштабируема по своей природе, так 2em будет равен 24pt, 0.5em будет равна 6pt и т. д. Использование «em» становятся все более популярным в веб-документах из-за масштабируемости и возможности с пользой применять в мобильных устройствах. 2. Pixels (px): «px» имеют фиксированный размер единиц, которые используются на экранах (например, для чтения на экране компьютера). Один пиксель равен одной точки на экране компьютера (самый малый элемент разрешения вашего экрана). Многие вебдизайнеры используют px в веб-документах в целях получения пиксель-идеального(pixel-perfect) представления своего сайта, отображаемого в браузере. Одна из проблем, с использованием px заключается в том, что эти единицы не позволяют изменять масштаб для слабовидящих читателей или мобильных устройств. 3. Points (pt): «pt», традиционно используются в печатных СМИ (все, что должно быть напечатано на бумаге, и т. д.). Один «pt» равен 1 / 72 дюйма. «pt», так же, как и «px», имеют фиксированный размер единицы и не могут масштабироваться. 4. Percents (%): Единицы измерения в % похожи на «em», за исключением нескольких принципиальных различий. Во-первых, текущий font-size равен 100% (т. е. 12pt = 100%). При использовании «%», ваш текст становится полностью масштабируемым для мобильных устройств и удобства пользователя (accessibility).

Как вы можете видеть, «em» и «%» увеличили размер шрифта, в то время как «px» и «pt» этого не сделали. Установка абсолютного размера для вашего текста может быть простым делом, но гораздо лучше для ваших посетителей использовать масштабируемый текст, который может быть отображен на любом устройстве или любой машине. По этой причине, единицы «em» и «%» предпочтительнее в использовании для текста вебдокумента.

«em» vs «%» Мы выяснили что единицы «px» и «pt», не лучшим образом подходят для веб-документов, что заставляет нас использовать «em» и «%». В теории, единицы «em» и «%» являются идентичными, но на практике они имеют незначительные различия, которые важно учитывать. В приведенном выше примере мы использовали в качестве базовой единицы font-size проценты (в тэге body). Если вы измените вашу базовую единицу font-size c «%» на «em» (то есть body {font-size: 1em;}), вы, должны бы не заметить разницы. Давайте посмотрим, что происходит, когда «1em» является нашей базовой единицей, и когда клиент меняет «Размер шрифта» в настройках своего браузера (такая возможность предусмотрена в во многих браузерах, например, Internet Explorer).

Итак, в чем же разница? Легко будет понять разницу между единицами font-size, когда вы увидите их в действии. Как правило, 1em = 12pt = 16px = 100%. При использовании этих размеров шрифта, давайте посмотрим, что происходит, когда вы увеличиваете базовый размер шрифта (с использованием CSS селектора body) от 100% до 120%.

habradigest #2 • октябрь 2008

Когда в браузере клиента размер текста установлен в «средней», то незаметно никакой разницы между «em» и «%». Однако, если параметр изменять, разница становится очень большой. При установке «Smallest» «em» гораздо меньше, чем «%», а при установке «Largest» нао-

39


WEB-РАЗРАБОТКА

борот «em» отображается гораздо большим, чем «%». И хотя многие утверждают, что единицы в «em» масштабируются так, как задумано, на практике текст в «em» масштабируется слишком резко, и при этом наименьший текст становится неразборчивым на некоторых машинах.

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

habradigest #2 • октябрь 2008

40


ПОСЛЕДНЯЯ СТРАНИЦА

Статистика Владимир Юнев

Работая в web и создавая в нем проекты, всегда интересно знать, что представляет собой средний пользователь интернета. Эта страничка будет содержать небольшую статистику по тому, как представлен наш читатель во всемирной паутине.

Операционные системы пользователей

Страны

Russia

68,35 %

Ukraine

13,67 %

Latvia

5,04 %

1.9 %

Belarus

2,16 %

189

0.2 %

United States

1,44 %

Symbian OS

37

>0.1 %

Lithuania

1,44 %

Sun Solaris

4

>0.1 %

(not set)

1,44 %

Unknown Unix system

3

>0.1 %

Uzbekistan

1,44 %

OS/2

2

>0.1 %

Armenia

0,72 %

Kazakhstan

0,72 %

Windows

70102

81.4 %

Linux

8272

9.6 %

Macintosh

5837

6.7 %

Unknown

1671

BSD

Браузеры и RSS-читалки пользователей Firefox

53.2 %

Opera

19.3 %

Safari

14.3 %

MS Internet Explorer

9.5 %

Mozilla

1.7 %

Unknown

0.6 %

Орфография и пунктуация авторская с небольшими правками.

FeedDemon (RSS Reader)

0.4 %

Проект habradigest:

Konqueror

0.2 %

Владимир «XaocCPS» Юнев - автор, редактор, верстка, сайт

Netscape

0.1 %

NetNewsWire (RSS Reader)

0.1 %

Владимир «OnTheFly» Синельников - дизайн, хостинг, домен

Others

0.1 %

habradigest #2 • октябрь 2008

Все статьи в настоящем издании публикуются с ресурса Хабрахабр, с любезного согласия авторов: SergeyRodyushkin, RomanNikitin, sirus, sunnybear, veter, DNovikov, tuta_larson, Yarc, Aesma, voicer, XaocCPS

habradigest, октябрь-ноябрь 2008

41


Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.