css
«Магический экран» на CSS
У всех в детстве была игрушка «магический экран»? Такая прямоугольная штуковина, обычно из красного пластика с двумя крутилками, которые надо было поворачивать, чтобы
А все знают, что браузеры на основе «вебкита» («Хром», «Сафари» и им подобные) некорректно отрабатывают некоторые комбинации CSS3-селекторов? Они «забывают» их перерисовывать, если меняется состояние, зависящее
Меня раздрадает этот баг, ему много лет, его не правят

Вчера ночью я про него думал и меня осенило каким образом баг можно использовать, чтобы создать
На основе этого бага я наколдовал «магический экран» без использования Джаваскрипта (не забудьте, работает в «Сафари» или «Хроме»). Если вести справа налево кружки́ рисуются, если слева направо — стираются. «Крутилки» только для декора, они не работают.
Шаманство и контакт с космосом происходит вот в этом участке CSS:
.outer>i:hover+i+i {
width: 10px;
height: 10px;
background: gray;
border-radius: 50%;
z-index: 2;
margin: -2px 0 0 -2px;
}Поле для рисования состоит из кучи элементов размером 3×3, когда я наезжаю курсором на один из них, срабатывает «:hover» и меняет стиль кружка́, который находится сильно после того, на который я наехал курсором. Это нужно, чтобы «вебкит» не увидел момента, когда курсор уходит с элемента. «Вебкит» хоть и неправильно работает с селекторами, тем не менее содержит кучу «хаков», чтобы в распространённых случаях всё выглядело как надо.
Таким образом, браузер не получает события, что курсор с элемента ушёл и оставляет стиль как есть — я увеличиваю размер элемента до 10×10 и меняю цвет на серый, на экране за курсором остаётся шлейф из таких элементов справа от курсора. Если же двигаться слева направо, наезжая на кружки, то браузер видит нужные события и очищает стиль, кружки пропадают.
В самых свежих «Сафари» 5.1 и «Хроме» 24 мой «магический экран» работает.
Отгадка: CSS не видно, картинка спрятана
«Уровень один» вчерашней головоломки прошли многие, этот ларчик открывается просто — есть RFC 5988, где описан способ подключать некоторые ресурсы через HTTP-заголовок. Этот RFC поддерживают «Опера» и «Файерфокс». На скриншоте, в средстве для разработчиков «Оперы» заголовок хорошо видно.

Дальше дело техники, пользуясь тем, что BODY в ДОМе есть всегда, вне зависимости от того указан ли он реально в документе, я накладываю на него стиль, выставляю размеры, обвожу бордюром и аутлайном
Теперь главное.
Есть такой стааааренький формат графики XMB, тестовый формат и по виду явно происходит от заголовочных файлов Си. Про него и не помнит уже никто, важно же в нём то, что
Вот начало одного из файлов, содержащих картинку в формате XBM:
#define _width 300
#define _height 225
static char _bits[] = {
0xbd, 0xf7, 0xde, 0x7b, 0xef, 0xbd, 0xf7, 0xde, 0x7b, 0xef, 0xbd, 0xf7, 0xde, 0x7b, 0xef, 0xbd, 0xf7, 0xde, 0x7b, 0xef, 0xbd, 0xf7, 0xde, 0x7b, 0xef,
0xbd, 0xf7, 0xde, 0x7b, 0xef, 0xbd, 0xf7, 0xde, 0x7b, 0xef, 0xbd, 0xf7, 0x0e, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00…
}Он тут, кстати, попроще, чем обычно используется, я выкинул название картинки, которое идёт перед подчёркиваниями. Браузеры, видимо, проще относятся к этому формату, работает и так.
У формата тяжёлая судьба, сначала его похоронил «Эксплорер», убрав в версии 6.0SP1 его поддержку
Мне давно хотелось стереть с него пыль
Вот часть CSS из вчерашнего примера:
background: 20px 20px url(bolk.css#0x5F0xBF0xFF0xDC0x530xF60x870…) no-repeat;
width: 80px; /*_width 80px*/
height: 80px; /*_height 80px*/
border: 7px dashed #259;
outline: #259 dashed 6px;
margin: 10px; padding: 3px;
}
Читатели правильно догадались, что после хеша в свойстве «background» записана картинка, но как она оттуда попадает в HTML не догадался никто.
В гонке выкидывания лишнего из картинки в формате XBM победила «Опера», ей нужно было только оставить первую строку «#define», которую она использует в качестве «магического» значения для определения формата,
Итак, откуда взялась картинка?
«Опера» смотрит в свойство «background» и видит там урл с картинкой, часть с хешем просто отбрасывается, в данном случае она не используется и браузер грузит картинку с адреса «bolk.css». «Опера» пытается определить в каком формате пришла картинка, тут нам помогает «магическая» строка «#define», раз она есть, стало быть это XBM.
Дальше браузер ищет в полученном файле «_width» и «_height» с цифрами и находит, считая цифры размерами картинки, после чего ищет первое вхождение «0x…» и двигается по ним, пока не находит их все, это и есть тело картинки, которую надо отобразить.
Головоломка для верстальщика: CSS не видно, картинка спрятана
Хотите голову поломать? Вот вам тест на знание всякого странного, откройте следующий урл в «Опере»:
Ответ завтра. Кажется, во всём интернете так ещё никто не делал.

Выше снимок того что вы должны увидеть на экране, я испытывал под «Оперой» последней версии (12.12) для «Мака», не факт, что под другими ОС всё так же будет.
Вот что видно в консоли, если попытаться скачать этот файл:
bolk@Bolk ~$ wget -O- --user-agent="Opera/9.80 (Macintosh; Intel Mac OS X 10.8.2) Presto/2.12.388 Version/12.12" /files/opera-mystery/
--2013-01-22 00:24:09-- /files/opera-mystery/
Resolving bolknote.ru... 91.230.61.15
Connecting to bolknote.ru|91.230.61.15|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 0 [text/html]
Saving to: ‘STDOUT’
[ <=> ] 0 --.-K/s in 0s
2013-01-22 00:24:09 (0.00 B/s) - written to stdout [0/0]Когда догадаетесь где тело страницы, переходите на уровень два — догадайтесь откуда берётся картинка.
Генерируемый задний фон
Я
10 PRINT CHR$(205.5+RND(1));: GOTO 10Выводит вот такой фон (скриншот с «Командора 64», поэтому цвета такие):

Для тех, кто не понимает этот архаичный язык, перепишу на Пайтоне:
import random; while 1: print random.choice(u'╱╲'),и ПХП:
for(;;) flush(print '/\\'[mt_rand(0, 1)+.5]);На «Хабре» статья пробегала, где автор пытался сделать то же при помощи CSS, не справился и сделал генерацию на серверной стороне — на PHP. Он высказывал мысль использовать для этого JS, но то ли не смог, то ли отбросил её
В общем, решил я попробовать сделать это на стороне клиента, на «канвасе», заодно посмотреть конструкции, которые позволяют ставить «канвас» в качестве задника, давно руки чесались.
Сначала результат (в левом верхнем углу фрагмент нормального размера):

Теперь посмотрим код. Я убрал всё лишнее, подробности можно посмотреть в разделе «Храню»:
!function(c) {
var ctx = document.getCSSCanvasContext ?
document.getCSSCanvasContext("2d", "maze", c.width, c.height):
c.getContext("2d");
ctx.font = "10px monospace";
ctx.fillStyle = '#36f';
ctx.fillRect(0, 0, c.width, c.height);
ctx.fillStyle = '#068';
for (var y = 7, ly = c.height; y < ly; y += 8) {
for (var x = -1, lx = c.width; x < lx; x+= 4) {
ctx.fillText("/\\".charAt(Math.random() + .5), x, y);
}
}
if (!document.mozSetImageElement && !document.getCSSCanvasContext) {
document.getElementsByTagName('body')[0].style.backgroundImage='url(' + c.toDataURL("image/png") + ')';
}
}(document.getElementById('maze'));Внутрь функции у меня передаётся объект «канваса» (размером 99×96, подогнан под шрифт).
В первой строке проверяется наличие метода «getCSSCanvasContext», он работает только в браузерах, основанных на Вебките (Хром, Сафари) и позволяет напрямую использовать «канвас» для задника, нужно только в CSS указать идентификатор:
body { background: -webkit-canvas(maze); }Если этот метод недоступен, то я рисую в скрытый тег «канваса», который есть у меня на странице. Вывожу те же самые символы, которые использовались в Бейсике.
Связь «канваса» и задника для браузеров на «Геко» (ФайрФокса и менее известных) в Джаваскрипте не видна, она полностью на стороне CSS:
body { background: -moz-element(#maze); }Эти браузеры позволяют указать в качестве задника что угодно — хоть IFRAME, был бы у него идентификатор. Соответственно, «maze» — идентификатор моего «канваса». Делать больше ничего не нужно, любой элемент с этим «айди» размножится в качестве фона.
В последних строках я проверяют доступен ли метод для указания «-
Масштабирование тега CANVAS
Думаю, многие знают, что у тега CANVAS можно задать размер, а потом смасштабировать его до нужных размеров при помощи CSS.
<canvas width="10" height="10" style="width:100px; height:100px"></canvas>В этом случае, он не меняет свой размер, доступный из кода, но на экране растягивается или сжимается до указанных величин. Иногда это бывает полезно, но есть недостаток — все браузеры, в которых я это пробовал, сглаживают («размывают») содержимое этого тега, что иногда нежелательно.
У меня нет возможности посмотреть под всеми распространёнными версиями браузеров, но, как выяснилось в эксперименте, с современными работает такой трюк, можно указать алгоритм масштабирования для изображений, который для «канваса» тоже работает:
canvas {
image-rendering: optimizeSpeed; /* old WebKit, Safari, FireFox 3.6+ */
image-rendering: -webkit-optimize-contrast; /* newer WebKit (CSS3 proposal) */
image-rendering: -o-crisp-edges; /* Opera 11.6+ (CSS4 prefixed) */
image-rendering: -moz-crisp-edges; /* FF 6+ (CSS4 prefixed) */
image-rendering: crisp-edges; /* CSS4 */
}
Чтобы была понятно о чём вообще речь — слева на скриншоте версия без сглаживания (с применёнными стилями), справа — со сглаживанием, это я прочертил одну линию:
with (canvas.getContext('2d')) {
moveTo(0, 0);
lineTo(10, 10);
stroke();
}Я снабдил код комментариями, чтобы было видно где это работает. К сожалению, в Эксплорере ничего не получится,
Добавлено позднее: похоже, у Хрома тоже проблемы. На разных ОС может работать или нет.
Теннис на CSS-анимациях

Я тут решил прикинуть для себя недостатки CSS-анимации, поэтому написал «теннис» на HTML и CSS. В кавычках, потому что играть в него невозможно: можно лишь управлять при помощи мышки «ракеткой», а шарик в этот момент будет летать из угла в угол, ни на что не реагируя.
Я было начал писать программу, которая рассчитает мне более длинную траекторию мяча, чтобы соударения выглядели повеселее, но поленился обработать все восемь комбинаций соударения, хотя вспомнить тригонометрию было весело.
Префиксы для всех браузеров в коде ставить не стал, оставил только для «вебкита» (будет работать в «Сафари», «Хроме» и «Яндекс.Браузере»), всё равно все браузеры собираются начать их поддерживать, так зачем же стараться?
Так вот, анимации в ЦСС — интересная штука, позволяющая простыми средствами плавно менять свойства объектов на странице, мне понравилось. Но у неё есть недостаток, на мой взгляд — анимируется ровно один элемент и у неё нет состояний.
Т. е. я не могу посмотреть в каком состоянии сейчас находится анимированный элемент, чтобы привязать к этому событию
По этой причине я не смог написать полноценный теннис — у меня просто нет средств, чтобы связать между собой положение мяча и ракетки.
Из других найденных недостатков — далеко не все свойства нормально анимируются, например, с фоновыми картинками
Кстати, гениальные ребята, которые для Интернет Эксплорера
Firefox 16 CSS hack
.selector, _:-moz-meter-optimum {
background: red;
}Интерактив и CSS
Я считаю, что с появлением анимации в CSS, интерактивность, достигаемая без использования JavaScript вышла на новый уровень. После экспериментов с вложенными повернутыми структурами, мне стало интересно посмотреть как выглядит анимация таких структур, хороши они тем, что там надо анимировать всего один стиль, а структура сама будет выделывать всякие дикие штуки.
Например, если анимировать градус поворота, то получится забавный червяк, закреплённый с одного конца. Легко можно сделать, чтобы он ползал и извивался буквой «S», если бы целую кучу префиксов не приходилось писать.

Но самое потрясающее — возможность добавлять интерактив, жаль только возможности браузеров в этом пока очень ограничены. Попробуйте поводить курсором мыши по червяку и закон его движения изменится, причём совершенно без JavaScript! Изменится случайным порядком — всё зависит от того как вы проведёте мышкой.
Устройство простое (если убрать лишнее):
@keyframes wag {
50% {
transform: rotate(10deg);
}
}
сегментхвоста {
/* остальные стили */
transform: rotate(-10deg);
animation-name: wag;
animation-duration: 3s;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
}
сегментхвоста:hover {
animation-name: none; /* отключаем анимацию, если навели мышкой */
}На наведение мышкой анимация отключается, от начала червяка до места наведения
Шрифт на чистом CSS
Десять лет назад я пытался решить проблему отсутствия у пользователей татарского шрифта (с Юникодом тогда было плохо, да и татарский туда не входил
Мощность компьютеров и возможности браузеров тогда были таковы, что такой HTML выводился буквально по слогам — компьютер нагружался сильно.
Сейчас такой проблемы нет, зато мощность компьютеров и браузеров возросли многократно. Я попробовал для интереса создать шрифт на чистом ЦСС. Там где можно пользовался только стилем бордюра, где нельзя — использовал

Правда я сделал ошибку — сначала создал все самые простые буквы, а потом уже начал делать сложные. Ближе к концу я начал терять интерес к происходящему и на некоторых буквах уже халтурил — некоторые из них отличаются по размеру.
Слова пишутся внутри тега TEXT, буквы повешены на однобуквенные теги, им соответствующие. Для наглядности, вот код, который напишет HELLO с моим стилем CSS:
<text><h></h><e></e><l></l><l></l><o></o></text>Цифр я изготовил всего две, они на тегах D1 и D8 (т. е. работают только однёрка и восьмёрка), кроме всего есть два знака препинания — точка (на теге DOT) и запятая (COMMA).
Обратите внимание, кстати, что в ИЕ можно такой шрифт создать уже с версии 5.5 — там есть фильтр matrix, при помощи которого можно эмулировать всё, что я использовал.
В ужатом состоянии (через оптимизатор CSSO) шрифт занимает всего 3,8КБ.
Вложено и повёрнуто

С тех пор как в браузерах появилась возможность свободно вращать любые объекты, мне нравится смотреть что из этого можно сделать, причём нравится экспериментировать с одинаковыми многократно вложенными объектами.
Т. е. поворот в CSS я задаю на один и тот же градус, но поскольку объекты вложены друг в друга, родительский угол тоже учитывается и получаются разнообразные кольца с регулярным рисунком. Если подключить к этому графику (тот же SVG), то всякие рождественские венки и прочие кольца со сложными, но повторяющимся рисунком, можно делать очень естественными образом.
Кстати, а если наложить несколько изображений с разным шагом и
Opera 12.50 CSS hack
Ребята из «Оперы» уже трудятся над весьма интересной версией 12.50 и у меня уже готов
@media (-o-min-device-pixel-ratio: 1/100) {
@media all {
body {
background: red; /* Opera 12.50+ */
}
}
}И ещё вариант:
_:-o-prefocus:fullscreen, body {
background: red; /* Opera 12.50+ */
}Вместо body надо подставить нужный вам селектор.
Opera 12 CSS hack
Вышла «Опера» 12, многое поменялось, на некоторых моих сайтах едут стили. В частности, на «Маке» «Опера» поменяла поведение стиля
Пришлось придумывать
Хак получился вот такой:
@media (min-resolution: .001dpcm) {
_:-o-prefocus, body { /* стиль только для Opera 12.00+ */
background: red;
};
}Соответственно, на место «body» надо поставить требуемый тег или селектор.
Перевод картинки в серый средствами CSS

Экспериментировал в рамках прототипа для рабочего проекта с преобразованием изображения в серую гамму. Вообще, конечно, есть тег CANVAS, там это запросто, но интересно посмотреть появилось ли
.gray {
filter: gray; /* IE 5+ */
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(grayscale=1)"; /* new IE */
}Я всё ждал когда же W3C «переизобретёт» микрософтские фильтры. Свершилось. Недавно появилась спецификация Filter Effects, которая, конечно, гораздо шире того, что
«Хром» включил в готовящуяся
.gray {
-webkit-filter: grayscale(1); /* Chrome 18+ */
}В «Опере» и «Файрфоксе» поддержки этих фильтров пока нет, но есть поддержка других — фильтров SVG. Можно, например, засунуть растровое изображение внутрь SVG и применить к нему нужный фильтр:
<svg style="width: 0; height: 0">
<filter id="gray">
<feColorMatrix type="saturate" values="0"/>
</filter>
</svg>
<!-- тут какой-то HTML код и вдруг понадобилась серая картика -->
<svg style="width:400px; height: 431px"><image xlink:href="/imgs/2008.11.27.jpg" width="100%" height="100%" filter="gray" /></svg>Хорошие новости — SVG можно мешать с HTML. С версии 3.5 в «Файерфоксе» есть способ лучше — можно применять фильры SVG прямо к тегам HTML (и делать всякие другие чудесные штуки), правда все описания в документации
<style>
.gray {
filter: url(#gray); /* Firefox 4+ */
}
</style>
<svg style="width: 0; height: 0">
<filter id="gray">
<feColorMatrix type="saturate" values="0"/>
</filter>
</svg>
<body><img src="/imgs/2008.11.27.jpg" class="gray" /></body>Добавлено позднее: в ходе дальнейших экспериментов оказалось, что в более ранних «Хромах» (нижнюю границу я пока не выяснил, но в моём
<style>
.gray {
-webkit-filter: url(#gray); /* Chrome ?? */
-webkit-filter: grayscale(1); /* Chrome 18+
}
</style>
<svg style="width: 0; height: 0">
<filter id="gray">
<feColorMatrix type="saturate" values="0"/>
</filter>
</svg>
<!-- тут HTML и внезапно понадобилась серая картинка -->
<svg style="width:400px; height: 431px"><image xlink:href="/imgs/2008.11.27.jpg" width="400" height="431" class="gray" /></svg>При этом «Хром» требует, чтобы ширина и высота картинки были заданы в абсолютных единицах (остальные браузеры прекрасно понимают проценты).
Если собрать всё вместе, получается нижеприведённый код. Я поставил рядом два изображения: слева обычный элемент HTML img, справа — растр в SVG, по ссылке можно протестировать что поддерживает ваш браузер. Мне негде потестировать IE и тем более десятую версию, но я Посмотреть код.
<html>
<meta http-equiv="X-UA-Compatible" content="IE=9" />
<style>
.gray {
cursor: pointer;
filter: url(#gray); /* Firefox 4+ */
filter: gray; /* IE 5+ */
-webkit-filter: url(#gray); /* Chrome ?? */
-webkit-filter: grayscale(1); /* Chrome 18+ */
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(grayscale=1)"; /* new IE */
}
.gray:hover {
filter: none;
-webkit-filter: none;
-ms-filter: none;
}
</style>
<svg style="width: 0; height: 0">
<filter id="gray">
<feColorMatrix type="saturate" values="0"/>
</filter>
</svg>
<body>
<img src="/imgs/2008.11.27.jpg" class="gray" />
<svg style="width:400px; height: 431px"><image xlink:href="/imgs/2008.11.27.jpg" width="400" height="431" class="gray" /></svg>
</body>
</html>
Как работает свойство vertical-align в ЦСС
На сайте
Как обойти баг со сложными селекторами CSS3 в «Вебките»
В «Вебките» (а значит в «Хроме» и «Сафари») есть баг с поддержкой сложных селекторов CSS3. Насколько мне рассказывали, это даже не баг, а сознательное решение. Относится он к сложным селекторам, связанным с «:target», «:checked» и ещё некоторым. Смысл в том, что у браузера как будто нет внутренних проверок на то, что условия поменялись (изменился элемент, на который должен указывать «:target», например) и надо переприменить селекторы к элементам.
Оказывается есть способ «подтолкнуть» браузер к этой проверке. Достаточно сделать пустую анимацию на нужном элементе. Вероятно это снижает производительность, так что осторожно, но зато работает:
section {
-webkit-animation: 0.1s hack infinite;
}
@-webkit-keyframes hack {
from {margin: 0; padding: 0;}
to {margin: 0; padding: 0;}
}Теперь в теге «section» эти сложные селекторы будут срабатывать.
CSS hack: firefox 3.5+
Для «Файерфокса» есть простой, не то чтобы даже
body {
background-color: red;
background-color: -moz-initial; /* для FF — значение свойства по-умолчанию */
}
.blk {
display: none; /* для всех браузеров блок скрыт, */
display: -moz-initial; /* кроме FF */
}Браузеры на основе «Вебкит» так же поддерживают это значение (со 125 сборки), правда безо всякого префикса.
Будущее формата CSS image

Мои эксперименты с картинками на чистом CSS внезапно дали следующий результат. Я взял для экспериментов логотип студии Лебедева в первом попавшемся цвете и перегнал его в CSS image при помощи своего конвертора, используя все префиксы браузеров: «webkit», «moz», «o» и «ms» («Конкерор» включать не стал, доля этого браузера исчезающе мала).
Оказалось, что если сжать получившийся файл (браузеры же понимают сжатый CSS), то получившийся файл будет размером меньше, чем та же картинка в формате PNG: 2,5КБ против 6,7. Файл с лого Студии я пропускал через онлайновый оптимизатор PNG (punypng), так что он вряд ли неоптимален.
Чем объяснить такой разврыв между форматом, предназначенном для эффективного сжатия графики, и неэффективным текстовым, с огромной избыточностью, сжатым обычным гзипом я не знаю.
Добавлено позднее: в комментариях картинку «утоптали» в 448 байт. Непонятно почему у меня такой плохой PNG получился.
Картинка на чистом CSS-2

Я тут всё это время думал как дёшево улучшить свой конвертор картинок в чистый CSS и придумал.
Выкинул 89 79. Это уже неплохо, в сжатом (gzip) виде этот код занимает 19КБ, что всего в два с половиной раза больше, чем исходное изображение в PNG. Результат можно сильно улучшить, если отказаться от повтора цвета, но изображения с мелкими деталями (с текстом, например) пострадают.
Кстати говоря, логотип студии Лебедева при конвертации из PNG (6,7КБ) уменьшился до 1,6КБ 1,3КБ. Это уже реальная экономия, если бы не префиксы браузеров.
Отказ от радиального градиента в пользу линейного позволил мне кодировать целую строку изображения одной конструкцией, что, в свою очередь, позволило сильно экономить на повторяющихся байтах (кстати, можно попробовать группировать пиксели по вертикали и горизонтали, а потом выбирать лучший вариант).
Кстати этот раз «Опера», увы, не смогла показать картинку, так как, похоже, не поддерживает позиционирование для линейных градиентов.
Добавлено позднее: сделал оптимизацию, теперь конвертор делает два прогона: один просматривает пиксели по горизонтали, второй по вертикали. Выбирается тот, который даёт наименьший результат.
Ключи запуска те же.
<?
// ffccbb → fcb
// ff0000 → red
// abcdef → abcdef
function Bo_shortcolor($color) {
static $shortnames = array(
"c0c0c0" => "silver",
"808080" => "gray",
"800000" => "maroon",
"ff0000" => "red",
"800080" => "purple",
"008000" => "green",
"808000" => "olive",
"000080" => "navy",
"008080" => "teal",
"f0ffff" => "azure",
"f5f5dc" => "beige",
"ffe4c4" => "bisque",
"a52a2a" => "brown",
"ff7f50" => "coral",
"ffd700" => "gold",
"808080" => "gray",
"008000" => "green",
"808080" => "grey",
"4b0082" => "indigo",
"fffff0" => "ivory",
"f0e68c" => "khaki",
"faf0e6" => "linen",
"800000" => "maroon",
"000080" => "navy",
"808000" => "olive",
"ffa500" => "orange",
"da70d6" => "orchid",
"cd853f" => "peru",
"ffc0cb" => "pink",
"dda0dd" => "plum",
"800080" => "purple",
"ff0000" => "red",
"fa8072" => "salmon",
"a0522d" => "sienna",
"c0c0c0" => "silver",
"fffafa" => "snow",
"d2b48c" => "tan",
"008080" => "teal",
"ff6347" => "tomato",
"ee82ee" => "violet",
"f5deb3" => "wheat",
);
$c = strtolower($color);
if (isset($shortnames[$c])) {
return $shortnames[$c];
}
if ($c[0] == $c[1] && $c[2] == $c[3] && $c[4] == $c[5]) {
return '#' . $c[1] . $c[3] . $c[5];
}
return '#' . $c;
}
class Bo {
public function __get($num) {
return $num ? $num . 'px' : 0;
}
}
function Bo_convert_helper($im, $lim1, $lim2, $getpix, $dotmask, $z) {
$end = $lim1 - 1;
$dots = array();
for ($o1 = 0; $o1<$lim2; $o1++) {
$coll = array();
$prev = null;
$start = 0;
for ($o2 = 0; $o2<$lim1; $o2++) {
$pixel = $getpix($im, $o2, $o1);
if ($prev !== null && $prev !== $pixel || $o2 == $end) {
if ($prev !== null) {
$color = Bo_shortcolor(vsprintf('%02x%02x%02x', $prev));
$coll[] = "$color {$z->$start},$color {$z->$o2}";
}
if ($o2 == $end && $prev != $pixel) {
$color = Bo_shortcolor(vsprintf('%02x%02x%02x', $pixel));
$cell[] = "$color {$z->$o2}";
}
$start = $o2;
}
$prev = $pixel;
}
$dots[] = sprintf($dotmask, implode(',', $coll), $z->$o1);
}
return $dots;
}
function Bo_convert($filename, $prefix = '') {
$z = new Bo();
if (!file_exists($filename) && !is_readable($filename)) {
throw new Exception('File not found.');
}
$types = array(
IMAGETYPE_JPG => 'jpeg',
IMAGETYPE_PNG => 'png',
IMAGETYPE_GIF => 'gif',
);
$imagedata = @getimagesize($filename);
if (!is_array($imagedata) || !isset($imagedata[2]) || !isset($types[$imagedata[2]])) {
throw new Exception('Unknown image format');
}
$im = call_user_func("imagecreatefrom" . $types[$imagedata[2]], $filename);
$sw = $w = imagesx($im);
$sh = $h = imagesy($im);
$hfunc = create_function('$im, $x, $y', 'return imagecolorsforindex($im, imagecolorat($im, $x, $y));');
$hmask = "{$prefix}linear-gradient(0,%s) 0 %s";
$hdots = join(',', Bo_convert_helper($im, $w, $h, $hfunc, $hmask, $z));
$vfunc = create_function('$im, $y, $x', 'return imagecolorsforindex($im, imagecolorat($im, $x, $y));');
$vmask = "{$prefix}linear-gradient(90,%s) %s 0";
$vdots = join(',', Bo_convert_helper($im, $h, $w, $vfunc, $vmask, $z));
if (strlen($hdots) > strlen($vdots)) {
$dots = $vdots;
$sw = 1;
} else {
$dots = $hdots;
$sh = 1;
}
return <<<HTML
<head>
<title>$filename converted in background by Evgeny Stepanischev //bolknote.ru</title>
<style text="text/css">
div {
background: $dots;
background-repeat: no-repeat;
{$prefix}background-size: {$sw}px {$sh}px;
background-size: {$sw}px {$sh}px;
width: {$w}px; height: {$h}px;
}
</style>
<body>
<div></div>
</body>
HTML;
}
if ($_SERVER['argc'] < 2) {
echo <<<HELP
Image to background CSS convertor v2 by Evgeny Stepanischev. //bolknote.ru Nov 2011
Usage: {$_SERVER['argv'][0]} filename [prefix]
filename - image file name (PNG, JPEG or GIF)
prefix - your browser name (Opera, IE, FF, Safari, Chrome)
HELP;
exit;
}
$browsers = array(
'ie' => '-ms-',
'opera' => '-o-',
'safari' => '-webkit-',
'chrome' => '-webkit-',
'chromium' => '-webkit-',
'ff' => '-moz-',
'firefox' => '-moz-',
);
if (isset($_SERVER['argv'][2])) {
$type = strtolower($_SERVER['argv'][2]);
$prefix = isset($browsers[$type]) ? $browsers[$type] : '-' . $type . '-';
} else {
$prefix = '';
}
try {
echo Bo_convert($_SERVER['argv'][1], $prefix);
} catch (Exception $e) {
echo "Error: ", $e->getMessage(), "\n";
exit(1);
};Цвета из CSS, названия которых короче их номеров
Кстати, если вдруг
Например, «silver» короче своего числового эквивалента «#c0c0c0» на один символ, а «tan» короче «#d2b48c» на четыре.
Картинка на чистом CSS

Сейчас CSS позволяет делать всё более интересные вещи. Раз уж сегодня короткий день, за 20 минут из «сэкономленного» часа накидал программу, которая конвертирует любую картинку (в форматах PNG, JPEG или GIF) в чистый CSS (без использования dataURI). Каждая точка кодируется через одну конструкцию
Если заморачиваться серьёзно, то можно выявлять в картинке области с градиентом (так делается в формате PNG для упаковки) и покрывать их целиком одной конструкцией, а так же оптимизировать наложением линейных градиентных областей с прозрачностью (через rgba или hsla). Достаточно интересная программисткая задача, кстати.
Самую очевидную оптимизацию я сделал в пробке по работе домой — выделил самый частый цвет и пустил его на фон, выиграл примерно 11,5КБ на картинке в примере.
За 20 минут я только успел сделать небольшой конвертор (результат работы можно посмотреть в разделе «Храню», только для «Хрома»). Так как синтаксис завязан на префиксы, то я в генераторе сделал указание браузера под который нужно сгенерировать код (поддерживаются: IE, Opera, Firefox и Chrome). Всё это весьма расточительно, картинка 75×75 занимает 164КБ.
У меня заработало для «Хрома» и «Файэрфокса», под «Оперой» не завелось, лень проверять что она не поддерживает, IE попробовать не могу — его не бывает на «Маках». Код конвертора
// Конвертор картинки в чистый CSS, / ноябрь 2011
<?
// ffccbb → fcb
// ff0000 → red
// abcdef → abcdef
function Bo_shortcolor($color) {
static $shortnames = array(
"#000080" => "navy",
"#008000" => "green",
"#008080" => "teal",
"#4b0082" => "indigo",
"#800000" => "maroon",
"#800080" => "purple",
"#808000" => "olive",
//"#808080" => "gray",
"#808080" => "grey",
"#a0522d" => "sienna",
"#a52a2a" => "brown",
"#c0c0c0" => "silver",
"#cd853f" => "peru",
"#d2b48c" => "tan",
"#da70d6" => "orchid",
"#dda0dd" => "plum",
"#ee82ee" => "violet",
"#f0e68c" => "khaki",
"#f0ffff" => "azure",
"#f5deb3" => "wheat",
"#f5f5dc" => "beige",
"#fa8072" => "salmon",
"#faf0e6" => "linen",
"#ff0000" => "red",
"#ff6347" => "tomato",
"#ff7f50" => "coral",
"#ffa500" => "orange",
"#ffc0cb" => "pink",
"#ffd700" => "gold",
"#ffe4c4" => "bisque",
"#fffafa" => "snow",
"#fffff0" => "ivory",
);
$c = strtolower($color);
if (isset($shortnames[$c])) {
return $shortnames[$c];
}
if ($c[0] == $c[1] && $c[2] == $c[3] && $c[4] == $c[5]) {
return '#' . $c[1] . $c[3] . $c[5];
}
return '#' . $c;
}
function Bo_convert($filename, $prefix = '') {
if (!file_exists($filename) && !is_readable($filename)) {
throw new Exception('File not found.');
}
$types = array(
IMAGETYPE_JPG => 'jpeg',
IMAGETYPE_PNG => 'png',
IMAGETYPE_GIF => 'gif',
);
$imagedata = @getimagesize($filename);
if (!is_array($imagedata) || !isset($imagedata[2]) || !isset($types[$imagedata[2]])) {
throw new Exception('Unknown image format');
}
$im = call_user_func("imagecreatefrom" . $types[$imagedata[2]], $filename);
$w = imagesx($im);
$h = imagesy($im);
// Самый частый цвет идёт на background-color
$colorfreq = array();
for ($x = 0; $x<$w; $x++) for ($y = 0; $y<$h; $y++) @$colorfreq[imagecolorat($im, $x, $y)]++;
$bg = array_search(max($colorfreq), $colorfreq);
for ($x = 0; $x<$w; $x++)
for ($y = 0; $y<$h; $y++) {
$pixel = imagecolorat($im, $x, $y);
if ($pixel != $bg) {
$color = Bo_shortcolor(sprintf('%06x', $pixel));
$dots[] = "{$prefix}radial-gradient($color,$color) {$x}px {$y}px";
}
}
$dots = join(',', $dots);
$bg = Bo_shortcolor(sprintf('%06x', $bg));
return <<<HTML
<head>
<title>$filename converted in background by Evgeny Stepanischev //bolknote.ru</title>
<style text="text/css">
div {
background: $dots;
background-repeat: no-repeat;
{$prefix}background-size: 1px 1px;
background-color: $bg;
background-size: 1px 1px;
width: {$w}; height: {$h};
}
</style>
<body>
<div></div>
</body>
HTML;
}
if ($_SERVER['argc'] < 2) {
echo <<<HELP
Image to background CSS convertor by Evgeny Stepanischev. //bolknote.ru Nov 2011
Usage: {$_SERVER['argv'][0]} filename [prefix]
filename - image file name (PNG, JPEG or GIF)
prefix - your browser name (Opera, IE, FF, Safari, Chrome)
HELP;
exit;
}
$browsers = array(
'ie' => '-ms-',
'opera' => '-o-',
'safari' => '-webkit-',
'chrome' => '-webkit-',
'chromium' => '-webkit-',
'ff' => '-moz-',
'firefox' => '-moz-',
);
if (isset($_SERVER['argv'][2])) {
$type = strtolower($_SERVER['argv'][2]);
$prefix = isset($browsers[$type]) ? $browsers[$type] : '-' . $type . '-';
} else {
$prefix = '';
}
try {
echo Bo_convert($_SERVER['argv'][1], $prefix);
} catch (Exception $e) {
echo "Error: ", $e->getMessage(), "\n";
};