πŸ“œ ⬆️ ⬇️

Implementing underscore styles in LESS via png generation in data-URI

I decided once to implement a flexible way to style the underlining of links β€” to simply make translucent underscores, adjust the pattern in dashed / dotted-border, make wavy underscores and generally have CSS3 text-decoration settings that no browser has yet.



The result is a PNG generator in the data-URI on LESS.

Demo .


')


Implementation options


Translucent, dotted and dotted underscores are quite simply done through border-bottom .

The interesting begins when you want to shift the line closer to the text.
You can build a view
 <a class="link"><span>Some link text here</span></a> 
and adjust the line-height of the span (or a ) element by setting it to display:inline-block , but then there is a problem on the multi-line text: the inline-block becomes a real block in terms of the border display (illustration on the right).

After thinking and experimenting, I came to the conclusion that the most β€œclean” and convenient solution would be to put an underline pattern in the background with a height equal to line-height . It remains only to understand where to get this pattern.

The most logical thing was to generate PNG dynamically and insert it into the data-URI. From a question on stackoverflow, it turned out that one person had already managed to generate a GIF image in one pixel ( here ), but I must say, very straightforward and inflexible: resizing this image would be a task equivalent to rewriting the entire code.

The weekend was breaking, and I finally decided to stop frustrating with the dirty implementation of underscore links and deal with the generation of PNGs.

PNG.js


After several hours of studying the specifications of PNG , ZLIB Data Format and DEFLATE Data Format , as well as an example of png serialization and a small reverse engineering ( here is an example of raw png generation ), a js-class was created for working with PNG suitable for cutting into pieces in LESS .

The PNG class can generate an uncompressed PNG with an indexed color (indexed-color) or a bitmap (truecolor with alpha). Used as follows:
PNG.js usecase
 <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), eg "000000ffffff" β‡’ black, white tRNS: trns //transparency string (alpha-values according to the palette colors, 1 byte per value), eg "00ff" β‡’ 0, 1 }, data: data //string of color indexes (or bitmap), 1 byte per color index, eg "00010100" β‡’ black, white, white, black }) result = png.toDataURL() //β‡’ data:image/png;base64,iV... </script> 



Running JS in LESS


As it turned out, LESS is very flexible for running JS. For example, functions can be run as follows:
  @test: `function(a){ return a }`; test: `(@{test})(3)`; //test: 3 


By moving png.js to the mix and writing the interface to it, the result is the following code:
painter.less
 //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(); } 



How to use?


1. Connect painter.less and less.js , as in the demo

 <link rel="stylesheet/less" type="text/css" href="painter.less" /> <script src="less.js" type="text/javascript"></script> 


2. Use classes for span elements:

 <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> 


3. Available mixins:



You can also use the .png(@stream: "0001", @w: 2, @h: 2, @color: black) by sending directly a stream of bits of indexed colors.

The result: a demo repository on github .

Source: https://habr.com/ru/post/181746/


All Articles