И реализовал, демо.
Варианты реализации
Полупрозрачное, пунктирное и точечное подчеркивания весьма просто делаются через border-bottom
☞.
Интересное начинается, когда хочется сместить линию ближе к тексту.
Можно соорудить конструкцию вида
<a class="link"><span>Some link text here</span></a>
и регулировать line-height
элемента span
(или a
), задав ему display:inline-block
, но тогда возникает проблема на многострочном тексте: inline-block
становится настоящим block’ом в плане отображения бордера (иллюстрация справа).
После размышлений и экспериментов, я пришел к выводу, что самым «чистым» и удобным решением было-бы класть паттерн подчеркивания в background
с высотой, равной line-height
. Осталось только понять, откуда брать этот паттерн.
- Генерировать картинку где-то на стороне и подключать её как файл — негибко и неудобно для разработки, каждое изменение будет убивать нервы.
- Использовать генератор PNG через canvas (такой, к примеру), но это также неудобно в разработке: каждый раз генерировать data-URI на стороне.
- Генерировать Repeating-gradient, но это весьма ненадежный способ, так как есть риск не попасть точно в пиксель линии подчеркивания, да и пунктирные подчеркивания не реализовать.
Самым логичным оставалось генерировать PNG динамически и вставлять в data-URI. Из вопроса на stackoverflow выяснилось, что один человек уже сумел генерировать GIF-картинку в один пиксель (тут), но, надо сказать, весьма прямолинейно и негибко: изменение размеров этой картинки было-бы задачей, равносильной переписыванию всего кода.
Гряли выходные, и я решил наконец перестать фрустрироваться грязной реализацией подчеркивания ссылок и разобраться с генерацией PNG.
PNG.js
После нескольких часов изучения спецификаций PNG, ZLIB Data Format и DEFLATE Data Format, а также примера сериреализации png и небольшого реверс-инжиниринга (тут пример генерации сырого png), был создан js-класс для работы с PNG, пригодный для распила на куски в LESS.
Класс PNG умеет генерировать несжатый PNG с индексированным цветом (indexed-color) или битмапа (truecolor with alpha). Используется следующим образом:
<script src="png.js"></script> <script> var png = new PNG(); png.set({ width: w, height: h, chunks: { PLTE: plte, //palette string (sequence of colors, 3 bytes per color), e.g. "000000ffffff" ⇒ black, white tRNS: trns //transparency string (alpha-values according to the palette colors, 1 byte per value), e.g. "00ff" ⇒ 0, 1 }, data: data //string of color indexes (or bitmap), 1 byte per color index, e.g. "00010100" ⇒ black, white, white, black }) result = png.toDataURL() //⇒ data:image/png;base64,iV... </script>
Запуск JS в LESS
Как оказалось, LESS весьма гибок для запуска JS. К примеру, функции можно запускать следующим обазом:
@test: `function(a){ return a }`; test: `(@{test})(3)`; //test: 3
Переместив png.js в примесь и написав интерфейс к нему, в итоге получился следующий код:
//Painting functions @text: black; @red: red; @green: green; .underline(@height: 20, @color: @text, @thickness: 1){ @patternGen: `function(h, thick){ var space = "", line = ""; //make line for (var i = 0; i < thick; i++){ line += "01" } //make space for (var i = 0; i < h - thick; i++){ space += "00" } return space + line; }`; @pattern: `(@{patternGen})(@{height}, @{thickness})`; .png(@stream: @pattern, @w: 1, @h: unit(@height), @color: @color); } .underline{ .underline(); } .underline.thick{ .underline(@thickness: 2); } .underline.offset{ } .underline.transparent{ .underline(@color: fade(@text, 30%), @thickness: 1); } .waved(@height: 20, @color: @red, @thickness: 2, @width: 4){ @patternGen: `function(h, w, thick){ var space = "", wave = ""; //make wave for (var y = 0; y < thick; y++){ for (var x = 0; x < w; x++){ if (x < w/2){ if (y < thick/2) { wave += "00" } else{ wave += "01" } } else { if (y < thick/2) { wave += "01" } else{ wave += "00" } } } } //make space for (var i = 0; i < (h - thick)*w; i++){ space += "00" } return space + wave; }`; @pattern: `(@{patternGen})(@{height}, @{width}, @{thickness})`; ptrn: @pattern; .png(@stream: @pattern, @w: unit(@width), @h: unit(@height), @color: @color); } .waved{ .waved(); } .waved.alt{ .waved(@color: @green, @thickness: 2, @width: 6); } .dotted(@height: 20, @color: @text, @width: 3, @thickness: 1){ @patternGen: `function(h, thick, w){ var space = "", line = ""; //make line for (var i = 0; i < thick; i++){ for(var x = 0; x < thick; x++){ line += "01"; } for(var x = thick; x < w; x++){ line += "00"; } } //make space for (var i = 0; i < (h - thick)*w; i++){ space += "00" } return space + line; }`; @pattern: `(@{patternGen})(@{height}, @{thickness}, @{width})`; .png(@stream: @pattern, @w: unit(@width), @h: unit(@height), @color: @color); } .dotted{ .dotted; } .dotted.rare{ .dotted(@width: 6); } .dotted.thick{ .dotted(@width: 6, @thickness: 2); } .dashed(@height: 20, @color: @text, @width: 8, @thickness: 1, @length: 4){ @patternGen: `function(h, thick, w, l){ var space = "", line = ""; //make line for (var i = 0; i < thick; i++){ for(var x = 0; x < l; x++){ line += "01"; } for(var x = l; x < w; x++){ line += "00"; } } //make space for (var i = 0; i < (h - thick)*w; i++){ space += "00" } return space + line; }`; @pattern: `(@{patternGen})(@{height}, @{thickness}, @{width}, @{length})`; .png(@stream: @pattern, @w: unit(@width), @h: unit(@height), @color: @color); } .dashed{ .dashed; } .dashed.rare{ .dashed(@width: 6); } .dashed.thick{ .dashed(@width: 10, @thickness: 2, @length: 6); } .dot-dashed(@height: 20, @color: @text, @width: 10, @thickness: 1){ @patternGen: `function(h, thick, w){ var space = "", line = ""; //make line for (var i = 0; i < thick; i++){ for(var x = 0; x < w; x++){ switch (true){ case (x > w*.75): line += "00"; break; case (x > w*.375): line += "01"; break; case (x > w*.125): line += "00"; break; default: line += "01"; } } } //make space for (var i = 0; i < (h - thick)*w; i++){ space += "00" } return space + line; }`; @pattern: `(@{patternGen})(@{height}, @{thickness}, @{width})`; .png(@stream: @pattern, @w: unit(@width), @h: unit(@height), @color: @color); } .dot-dashed{ .dot-dashed; } .dot-dashed.thick{ .dot-dashed(@width: 10, @thickness: 2); } .pattern(@height: 20, @color: @text, @width: 8, @thickness: 1, @length: 4, @pattern: ". -"){ } //Mixin that generates PNG to background .png(@stream: "0001", @w: 2, @h: 2, @color: black){ @r: red(@color); @g: green(@color); @b: blue(@color); @hexColor: rgb(red(@color),green(@color),blue(@color)); @PLTE: `"ffffff" + ("@{hexColor}").substr(1)`; //Make bytes palette: first-white, rest-passed color; @a: alpha(@color); @tRNS: `"ff" + (function(){ var a = Math.round(@{a} * 255).toString(16); return (a.length == 1 ? "0" + a : a) })()`; //png.js: https://github.com/dfcreative/graphics/blob/master/src/PNG.js @initPNG: `(function(){ /*...copy-pasted png.js: https://github.com/dfcreative/graphics/blob/master/src/PNG.js */)()`; @background: `(function(){ var png = new PNG(); png.set({ width: @{w}, height: @{h}, chunks:{ PLTE: @{PLTE}, tRNS: @{tRNS} }, data: @{stream} }) return "url(" + png.toDataURL() + ")"; })()`; background-image: ~"@{background}"; } .png{ .png(); }
Как использовать?
1. Подключить painter.less
и less.js
, как в демо
<link rel="stylesheet/less" type="text/css" href="painter.less" /> <script src="less.js" type="text/javascript"></script>
2. Использовать классы для span-элементов:
<span class="underline">Простое подчеркивание</span> <span class="underline thick">Толcтое подчеркивание</span> <span class="underline offset">Смещенное подчеркивание</span> <span class="underline transparent">Полупрозрачное подчеркивание</span> <span class="waved">Волнистое подчеркивание</span> <span class="waved alt">Волнистое подчеркивание 2</span> <span class="dotted">Точечное частое подчеркивание</span> <span class="dotted rare">Точечное редкое подчеркивание</span> <span class="dotted thick">Точечное толстое подчеркивание</span> <span class="dashed">Пунктирное подчеркивание</span> <span class="dashed thick">Пунктирное толстое подчеркивание</span> <span class="dot-dashed">Штрих-пунктирное подчеркивание</span>
И отрегулировать позицию background:
span { background-posiion: 0 -5px; }
3. Доступные миксины:
.underline(@height: 20, @color: @text, @thickness: 1)
.waved(@height: 20, @color: @red, @thickness: 2, @width: 4)
.dotted(@height: 20, @color: @text, @width: 3, @thickness: 1)
.dashed(@height: 20, @color: @text, @width: 8, @thickness: 1, @length: 4)
.dot-dashed(@height: 20, @color: @text, @width: 10, @thickness: 1)
Можно также использовать миксин .png(@stream: "0001", @w: 2, @h: 2, @color: black)
, отправляя напрямую поток битов индексированных цветов.
Итог: демо, painter.less, png.js.
ссылка на оригинал статьи http://habrahabr.ru/post/181746/
Добавить комментарий