интернет магазин швейные машины и бытовые вышивальные вязальные | продажа котеджей, загородный дом | Лодки надувные резиновые - статьи и консультации. | Аборт. Прерывание беременности профессионально и они. Прерывание. | недвижимость - купить дома северный Кипр карта
Мешки с песком
AVIS media / web&multimedia / Статьи о дизайне и разработке сайтов / Мешки с песком: разрезать, поделить

Мешки с песком: разрезать, поделить

Роб Суон

Перевод оригинальной статьи Роба Суона Sliced and Diced Sandbags

Translated with the permission of A List Apart Magazine

Перевод Кирилла Грушко

Сельская местность Сассекса словно создана для прогулок. Во время одного такого моциона среди холмов мне вдруг подумалось: как бы исхитриться, чтобы текст на веб-странице автоматически обтекал изображения с неровными краями? И придумал. Почаще выбирайтесь на свежий воздух!

Идея.

Придумка сама по себе проста. Создадим ряд «мешков с песком» div, наложим их на изображение, и текст будет обтекать не изображение, а «мешки с песком». Вручную проделывать такую работу утомительно. Почему бы не поручить её PHP?

Первый шаг: вычисление размеров «мешков с песком».

Начнем с создания массива размеров и координат наших воображаемых «мешков». Идеальные «мешки» имели бы высоту в 1 пиксель, что обеспечило бы наиболее плавное обтекание текстом, однако вычисление «на коленке» реальных размеров «мешков» – задача, мягко говоря, абсурдная. Остается уповать на подходящий способ автоматизации процесса.

Возьмем для примера нижеследующий рисунок и разместим его справа от текста:


Пример изображения с текстом, выключенным по левому краю.

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

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

В переводе на совсем уж технический язык, наша затея выглядит так:

<?php

$image = imagecreatefrompng( 'an_image.png' );
$width = imagesx( $image );
$height = imagesy( $image );

for ( $y=0; $y < $height; $y++ ){
$imagemap[$y] = 0;
for ( $x=0; $x < $width; $x++ ){
$c = imagecolorsforindex( $image, »
imagecolorat( $image, $x, $y ) );
if ( $c['alpha'] < 127 ){
break;
} else {
$imagemap[$y]++;
}
}
}

?>

Ну вот и покончено с самым сложным. Как вам нравится функция imagecolorsforindex – неплохо, не правда ли? Она выдает массив из красных, зеленых, синих и альфа-компонентов. Если альфа-компонент пикселя меньше 127, значит пиксель не полностью прозрачен.

Расставляем «мешки».

Между прочим, Internet Explorer не понимает div высотой всего в 1 пиксель. Так что без небольшого вкрапления CSS здесь не обойтись. Чтобы Internet Explorer проглотил наш однопиксельный div, создадим следующую таблицу стилей:

.sandbag-right {
border: 0;
padding: 0;
font-size: 0;
margin: 0 0 -1px 0;
height: 2px;
float: right;
clear: right;
background: red;
}

Установив font-size равным нулю, создадим div высотой в 2 пикселя. Затем, пока Internet Explorer не успел опомниться, присвоим нижнему краю изображения отрицательное значение в -1 пиксель, и таким образом div обретет требуемую высоту в 1 пиксель. Ура!

(Красный фон добавлен с целью отчетливо рассмотреть мешки; позднее мы от него избавимся).

Укладываем «мешки».

Теперь всё на месте, давайте наконец создадим «мешки». С этой целью определим для массива imagemap незамысловатый цикл foreach. Уточню, что в качестве ширины «мешка» не следует указывать значение из нашего массива, ибо в сущности мы имеем лишь величину пустого пространства возле «мешка». Ширина самого «мешка» – это ширина изображения минус значение из нашего массива.

Используем функцию printf чтобы вывести код нашего «мешка» в шаблон. Это облегчит дальнейшую работу:

<?php

$sandbagTemplate = '<div class="sandbag-right" »
style="width: %dpx;"></div>';

foreach ( $imagemap as $position => $blankPixels ){
$sandbagWidth = $width-$blankPixels;
printf( $sandbagTemplate,$sandbagWidth );
}

?>


Первый шаг: предварительное обтекание текстом.

Здесь можно увидеть код для первого шага.

Второй шаг: крушение идеалов…

Итак, у нас получился массив, позволяющий создать замечательные, можно сказать – совершенные «мешки». Однако, протестировав код, мы с удивлением обнаруживаем, что в бренном мире совершенства нет: «мешки» добротно выполняют свое предназначение лишь в браузере Opera. В других браузерах текст выходит из-под контроля и наползает на изображение.

Пробуем поиграться с шириной полей – безрезультатно. Приходится заключить, что нам потребуются менее совершенные «мешки», высотой более 1 пикселя. Насколько более, пусть каждый решает для себя. Я нахожу вполне уместным задать высоту от 10 до 50 пикселей, однако высота может варьироваться, так что предлагаю не мучиться с выбором и задать в функции широкий диапазон значений.

Научившись обманывать Internet Explorer и создавать однопиксельные «мешки», мы, конечно, немного поторопились, ибо теперь в них нет большого смысла, и наша функция станет более пригодной, если установить минимальную высоту «мешка» в 2 пикселя. Неприятно отказываться от столь замечательного трюка, поэтому сделаем глубокий вдох… и разом покончим с однопиксельными «мешками». Однако, не спешите расстраиваться: данный фокус пригодится в дальнейшем.

Несовершенные «мешки».

Теперь создадим цикл для уже существующего массива. Предположим, что высота каждого «мешка» равна 10 пикселям; стало быть, массив следует разбить на кластеры, кратные 10, и, встречая в каждом кластере самый большой «мешок», выводить его значение в новый массив. Разумеется, вместо использования некоего конкретного значения, употребим уже знакомую переменную sandbagHeight. Обратите внимание, что самому большому «мешку» в каждом кластере соответствует наименьшее значение в массиве, ибо массив представляет собой прозрачное поле, а вовсе не настоящий мешок. Исходя из этого, воспользуемся удобной функцией min из арсенала PHP, для получения из массива наименьшего значения. И вот результат:

for( $i=0;$i &lt; count($imagemap ); $i = $i+$sandbagHeight) {
for( $x=0; $x < $sandbagHeight; $x++ ){
$b = $x + $i;
if( isset( $imagemap[$b] ) ){
$section[$b] = $imagemap[$b];
}
}
$sandbag[] = min( $section );
unset( $section );
}

Теперь потребуется указать высоту каждого «мешка». Это нетрудно сделать, добавив её значение к шаблону «мешков»:

$sandbagTemplate = '<div class="sandbag-right" »
style="width: %dpx; height: %dpx"></div>';

Следует также «подпереть» первый и последний «мешки» в массиве, дабы иметь возможность изолировать их и при необходимости назначить им дополнительный стиль.

Используя для создания мешков новый массив sandbag вместо старого imagemap, получим вот что:


Второй шаг: «мешки» в пустоте.

Код для второго шага можно увидеть здесь.

Третий шаг: подкладываем под «мешки» изображение.

Осталось только подложить рисунок под «мешки с песком». Что может быть проще? Стоит лишь… эээ… ну, в общем… М-да. Я-то полагал, что проще задачи не найти: можно присвоить окружающий div фоновому изображению, или, на худой конец, использовать img. Выходит, сделать это не так уж просто.

Как только мы присвоим окружающему div определенное значение (с целью закрепить за ним фоновый рисунок), как текст начинает обтекать сам div! Этот оператор, оказывается, сильнее наших мешков. То же справедливо и в отношении img. Попытки использовать слои z-index к добру не привели. Наблюдая, как текст «плывет» в том или ином браузере, я, признаться, впал в отчаяние. Наши сногсшибательные «мешки» прекрасно справляются со своей задачей, но поместить перед ними изображение, не разрушая функциональности, решительно невозможно!

Подручные средства.

Так давайте ничего и не помещать перед «мешками»! Пошевелим мозгами: а что если использовать сами «мешки»? Почему бы не присвоить каждому из них одно и то же фоновое изображение, и не разместить это изображение соответственно расположению каждого мешка? Помогает? Лучше некуда.


Третий шаг: мешки с изображением.

Код для шага 3 здесь.

Четвертый шаг: фиктивный атрибут alt.

Наконец-то наш скрипт работает. Следует, однако, признать: проделывая всяческие пируэты, мы утратили внешнюю привлекательность. Но волноваться ни к чему: просто присвоим изображению фиктивные атрибуты alt и title. Возьмем их из функции и поместим в окружающий div в качестве title. Затем вставим всё это непосредственно перед первым «мешком», под видом скрытого span. Таким образом, при отключении таблицы стилей на экран выведется текст alt, а при наведении на изображение указателя мыши – всплывет атрибут title.

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

Теперь таблица стилей выглядит так:

.sandbagImage span {
display: none;
}

.sandbagRight {
border: 0;
padding: 0;
font-size: 0;
margin: 0 0 0 25px;
float: right;
clear: right;
background: no-repeat;
}

Для создания окружающего div используем следующий код, дающий возможность применить атрибут alt:

if($alt != ''){
echo '<div class="sandbag-image" title="' . $alt .
'"><span>' . $alt .'</span>';
} else {
echo '<div class="sandbag-image">';
}

(Видеть код целиком нет необходимости, ибо поправка весьма незначительна).

Пятый шаг: пусть Safari будет счастлив.

Между тем, назревает война двух браузеров. В Safari no-repeat не срабатывает при присвоении местоположению фонового изображения отрицательной величины: если последний «мешок» слишком велик, в нем воспроизводится верхний край изображения. Во избежание подобного недоразумения потребуется рассчитать размер каждого «мешка», добавляемого в массив, при этом последняя добавленная переменная станет искомым размером последнего «мешка» в массиве. Осуществить задуманное просто: достаточно добавить во второй цикл вычислительную функцию:

for( $i=0;$i &lt; count( $imagemap ); $i = $i+$sandbagHeight ){
for( $x=0;$x &lt; $sandbagHeight; $x++ ){
$b = $x + $i;
if( isset( $imagemap[$b] ) ){
$section[$b] = $imagemap[$b];
}
}
$sandbag[] = min( $section );
$finalSectionSize = count( $section )-1;
unset( $section );
}


Четвертый шаг: «мешки», дружественные Safari.

Взгляните на код четвертого шага.

Осчастливим Internet Explorer.

И тем не менее мы все еще не избавились от no-repeat. Почему? Предположим, что имеется изображение высотой в 121 пиксель, разделенное на «мешки», каждый высотой в 10 пикселей. Последний мешок, стало быть, будет иметь высоту в 1 пиксель.

Как известно, Internet Explorer не понимает div высотой в 1 пиксель, если только не прибегнуть к трюкам CSS. Присваивая последнему «мешку» отрицательное значение, мы обрекаем себя на добавление «ложных мешков» для получения необходимого значения нижнего края изображения.

Подобных мучений хотелось бы избежать, и все же без no-repeat здесь не обойтись, поскольку в только что описанной ситуации он предотвращает повторение в нижней строке изображения верхней строки пикселей.

А вот и код для пятого шага.

Шестой шаг: выключка по левому краю.

Всё, о чем говорилось выше, годится для текста, выровненного (выключенного) по правому краю. При выравнивания влево, следует помнить о некоторых простых истинах:

CSS для наших «мешков» должен обтекаться слева, а не справа.

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

При размещении фонового изображения, отсчет оси X всегда начинается с нулевого пикселя.

И наконец…

Результирующая функция.

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

<?php

alignedImage( 'an_image.png', 'right', »
'A right aligned blob',30 );

?>

Код результирующей функции см. здесь. Конец трудам!

Об авторе. Роб Суон – почитатель веб-стандартов, скромный разработчик Интернет в Сассекском университете. Любит представляться бывшим тестировщиком видеоигр. Имеет философскую степень, способствующую неординарному подходу к программированию. Недавно основал компанию под названием «Fuelled on Coffee, Ltd.».