├── LICENSE ├── README.md ├── jquery.slickwrap.js └── test ├── escalator.png └── index.html /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2009 Jason Wyatt Feinstein 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jQSlickWrap 2 | 3 | Provides "slick" wrapping around images using jQuery. See more at http://www.jwf.us/projects/jQSlickWrap 4 | -------------------------------------------------------------------------------- /jquery.slickwrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jQSlickWrap v0.2 3 | * The MIT License 4 | * 5 | * Copyright (c) 2012 Jason Wyatt Feinstein 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | */ 25 | 26 | 27 | (function($){ 28 | var logging = false; 29 | 30 | if($ == null){ 31 | throw new Error("jQuery must be available for jQSlickWrap to be "+ 32 | "activated."); 33 | } 34 | if($.fn.slickwrap){ 35 | throw new Error("A plugin which introduces the function 'slickwrap' to "+ 36 | "the NodeList object already exists and jQSlickWrap will not "+ 37 | "function."); 38 | } 39 | 40 | 41 | function log(){ 42 | if(window.console && logging){ 43 | if(window.console.log){ 44 | window.console.log.apply(console,Array.prototype.slice.call(arguments)); 45 | } 46 | } 47 | } 48 | 49 | /** 50 | * Meat and potatoes of this plugin, it is called on each image. 51 | * 52 | * @scope 53 | * The node for the image to slickwrap. 54 | */ 55 | function slickWrapImage(settings){ 56 | log("Inside %o calling %s()", this, arguments.callee.name); 57 | var $image = $(this); 58 | var $parent = $image.parent(); 59 | var floatDirection = $image.css("float"); 60 | log(" $(this)=%o, $(this).parent()=$o, floatDirection=%s", 61 | $image, $parent, floatDirection); 62 | 63 | if(floatDirection != "left" && floatDirection != "right"){ 64 | return; 65 | } 66 | 67 | 68 | if($image.css("display")=="none") return; //@AlmogBaku: If object is hidden do not wrap it! 69 | 70 | /* 71 | * Find the padding all the way around. 72 | */ 73 | var padding = { 74 | top: $image.css('padding-top'), 75 | right: $image.css('padding-right'), 76 | bottom: $image.css('padding-bottom'), 77 | left: $image.css('padding-left') 78 | }; 79 | padding.top = parseInt(padding.top.replace(/[^\d]*/g, ""), 10); 80 | padding.right = parseInt(padding.right.replace(/[^\d]*/g, ""), 10); 81 | padding.bottom = parseInt(padding.bottom.replace(/[^\d]*/g, ""), 10); 82 | padding.left = parseInt(padding.left.replace(/[^\d]*/g, ""), 10); 83 | 84 | /* 85 | * Create a canvas and draw the image onto the canvas. 86 | */ 87 | var canvas = document.createElement("CANVAS"); 88 | canvas.width = $image.width()+padding.left+padding.right; 89 | canvas.height = $image.height()+padding.top+padding.bottom; 90 | var width = canvas.width; 91 | var height = canvas.height; 92 | var context = canvas.getContext("2d"); 93 | 94 | /* 95 | * Draw the image once in the top-left, so we can grab the background 96 | * color 97 | */ 98 | context.drawImage(this, 0, 0); 99 | var imageData = context.getImageData(0, 0, width, height); 100 | var data = Array.prototype.slice.call(imageData.data); 101 | if(settings.bgColor == null){ 102 | settings.bgColor = { 103 | r: data[0], 104 | g: data[1], 105 | b: data[2], 106 | a: data[3] 107 | }; 108 | } 109 | /* 110 | * Fill the whole image with the background color, then try again. 111 | */ 112 | context.clearRect(0, 0, width, height); 113 | context.fillStyle = "rgba("+settings.bgColor.r+","+settings.bgColor.g+","+settings.bgColor.b+","+settings.bgColor.a+")"; 114 | context.fillRect(0, 0, width, height); 115 | context.drawImage(this, padding.left, padding.top); 116 | 117 | /* 118 | * Set the parent's background-image to this image. 119 | */ 120 | if(settings.sourceImage) { //using source image instead of canvas 121 | $parent.css({ 122 | "background-image": "url("+$image.attr("src")+")", 123 | "background-position": padding.left+"px "+padding.top+"px", 124 | "background-repeat": "no-repeat" 125 | }); 126 | 127 | /* 128 | * If the float is right it might calculate the backgorund-position based the width of the parent 129 | */ 130 | if(floatDirection=="right") { 131 | $parent.css("background-position", ($parent.width()-padding.right-$image.width())+"px "+padding.top+"px"); 132 | 133 | /* 134 | * In case of resizing it MUST calculate the background-position again. 135 | */ 136 | $(window).resize(function() { 137 | $parent.css("background-position", ($parent.width()-padding.right-$image.width())+"px "+padding.top+"px"); 138 | }); 139 | } 140 | } else { 141 | $parent.css({ 142 | "background-image": "url("+canvas.toDataURL()+")", 143 | "background-position" : "top "+floatDirection, 144 | "background-repeat" : "no-repeat" 145 | }); 146 | } 147 | 148 | var divWidths = calculateDivWidths.call(this, canvas, padding, settings.bgColor, settings.resolution, settings.bloomPadding, settings.cutoff); 149 | var divs = []; 150 | var divHeight = settings.resolution; 151 | var divWidths_length = divWidths.length; 152 | for(var i = 0; i < divWidths_length; i++){ 153 | divs.push('
'); 156 | } 157 | $parent.prepend(divs.join("")); 158 | 159 | /* 160 | * Adjust the height of the parent element just in case it's too 161 | * short. If we didn't do this the new background image could 162 | * get cut off. 163 | */ 164 | var parentHeight = $parent.height(); 165 | var imageHeight = $image.height()+padding.top; 166 | $parent.css({ 167 | minHeight: imageHeight 168 | }); 169 | 170 | /* 171 | * Hide the image itself. 172 | */ 173 | $image.css("display", "none"); 174 | } 175 | 176 | /** 177 | * Calculates the widths of the divs for slickwrapping. 178 | * 179 | * @scope 180 | * The node for the image to slickwrap. 181 | */ 182 | function calculateDivWidths(canvas, padding, bgColor, resolution, bloomPadding, cutoff){ 183 | var $image = $(this); 184 | var $parent = $image.parent(); 185 | var floatDirection = $image.css("float"); 186 | var lineHeight = resolution; 187 | var width = canvas.width; 188 | var height = canvas.height; 189 | 190 | log("Padding: ", padding); 191 | 192 | /* 193 | * Get drawing context for canvas... 194 | */ 195 | var context = canvas.getContext("2d"); 196 | 197 | /* 198 | * Algorithm: 199 | * 200 | * 1. Get the image data 201 | * 2. Threshold the image from the background color and bloom it by the 202 | * right padding size... 203 | * 3. Iterate in lineHeight-sized intervals to find the width of the 204 | * image from one edge (depending on floatDirection) 205 | * 1. Put those widths into an array. 206 | * 4. Return the array. 207 | */ 208 | 209 | /* 210 | * Step 1 211 | */ 212 | var image = context.getImageData(0, 0, width, height); 213 | var data = Array.prototype.slice.call(image.data); 214 | if(bgColor == null){ 215 | bgColor = { 216 | r: data[0], 217 | g: data[1], 218 | b: data[2], 219 | a: data[3] 220 | }; 221 | } 222 | 223 | /* 224 | * Step 2 225 | */ 226 | // Threshold the image 227 | var data_length = data.length; 228 | for(var i = 0; i < data_length; i=i+4){ 229 | var distance = { 230 | r: Math.abs(bgColor.r - data[i]), 231 | g: Math.abs(bgColor.g - data[i+1]), 232 | b: Math.abs(bgColor.b - data[i+2]), 233 | a: Math.abs(bgColor.a - data[i+3]) 234 | }; 235 | if(distance.r < cutoff && distance.g < cutoff && distance.b < cutoff && distance.a < cutoff){ 236 | data[i+3] = 0; 237 | } else { 238 | data[i+3] = 255; 239 | } 240 | } 241 | // If bloomPadding is set to true, bloom the image. 242 | if(bloomPadding){ 243 | var bloom_size = (floatDirection == "left" ? padding.right : padding.left); 244 | for(var i = 0; i < bloom_size; i++){ 245 | var bloomedData = []; 246 | for(var y = 0; y < height; y++){ 247 | for(var x = 0; x < width; x++){ 248 | var dataLocation = (x+y*width)*4+3; 249 | if(y-1 >= 0){ 250 | var upLocation = (x+(y-1)*width)*4+3; 251 | bloomedData[dataLocation] = data[upLocation] > 0 ? 255 : bloomedData[dataLocation]; 252 | } 253 | if(y+1 < height){ 254 | var downLocation = (x+(y+1)*width)*4+3; 255 | bloomedData[dataLocation] = data[downLocation] > 0 ? 255 : bloomedData[dataLocation]; 256 | } 257 | if(x-1 >= 0){ 258 | var leftLocation = (x-1+y*width)*4+3; 259 | bloomedData[dataLocation] = data[leftLocation] > 0 ? 255 : bloomedData[dataLocation]; 260 | } 261 | if(x+1 < width){ 262 | var rightLocation = (x+1+y*width)*4+3; 263 | bloomedData[dataLocation] = data[rightLocation] > 0 ? 255 : bloomedData[dataLocation]; 264 | } 265 | } 266 | } 267 | data = bloomedData.slice(); 268 | } 269 | } 270 | 271 | /* 272 | * Step 3 273 | */ 274 | var paddingSize = bloomPadding ? 0 : (floatDirection == "left" ? padding.right : padding.left); 275 | 276 | var result = []; 277 | var rows = height / lineHeight; 278 | rows = height % lineHeight == 0 ? rows : rows + 1; 279 | for(var row = 0; row < rows; row++){ 280 | var maxWidth = 0; 281 | 282 | /* 283 | * Calculate the start and end positions for the loops. 284 | */ 285 | var startX = floatDirection == "right" ? 0 : width-1; 286 | var endX = floatDirection == "right" ? width-1 : 0; 287 | var startY = row * lineHeight; 288 | var endY = startY + lineHeight-1; 289 | endY = endY >= height ? height-1 : endY; 290 | 291 | if(floatDirection == "right"){ 292 | for(var y = startY; y <= endY; y++){ 293 | var offset = y*(width*4); 294 | var foundAt = width - x; 295 | for(var x = startX; x <= endX; x++){ 296 | var location = x*4 + offset; 297 | if(data[location+3] == 255){ 298 | foundAt = width - x; 299 | break; 300 | } 301 | } 302 | if(foundAt > maxWidth){ 303 | maxWidth = foundAt; 304 | } 305 | } 306 | } else { 307 | for(var y = startY; y <= endY; y++){ 308 | var offset = y*(width*4); 309 | var foundAt = 0; 310 | for(var x = startX; x >= endX; x--){ 311 | var location = x*4 + offset; 312 | if(data[location+3] == 255){ 313 | foundAt = x; 314 | break; 315 | } 316 | } 317 | if(foundAt > maxWidth){ 318 | maxWidth = foundAt; 319 | } 320 | } 321 | 322 | } 323 | 324 | result.push(maxWidth+(maxWidth != 0 ? paddingSize : 0)); 325 | } 326 | 327 | /* 328 | * Step 4 :) 329 | */ 330 | return result; 331 | } 332 | 333 | /* 334 | * Make the plugin. 335 | */ 336 | $.fn.slickWrap = function(args){ 337 | var settings = { 338 | bgColor: null, 339 | bloomPadding: false, 340 | resolution: 20, 341 | cutoff: 5, 342 | sourceImage: false //using the "src" from the image to fill the parent background 343 | }; 344 | $.extend(settings, args); 345 | 346 | return this.each(function(i){ 347 | /* 348 | * If the node isn't an image, skip it. 349 | */ 350 | if(this.tagName != "img" && this.tagName != "IMG"){ 351 | return; 352 | } 353 | 354 | /* 355 | * If the image has been loaded, go ahead and operate on it, 356 | * otherwise wait until it's loaded by binding to the 'load' event. 357 | */ 358 | if(this.complete){ 359 | log("%o was already loaded, calling "+ 360 | "slickWrapImage.call(this)", this); 361 | 362 | slickWrapImage.call(this, settings); 363 | } else { 364 | log("%o wasn't loaded yet, calling "+ 365 | "bind('load', slickWrapImage)", this); 366 | 367 | $(this).bind("load", function(){ 368 | slickWrapImage.call(this, settings); 369 | }); 370 | } 371 | }); 372 | }; 373 | 374 | /* 375 | * Make a utility plugin for finding line-height of an element. 376 | */ 377 | $.fn.slickWrapLineHeight = function(){ 378 | return 12; 379 | }; 380 | 381 | /* 382 | * If the browser doesn't support canvas, skip everything by re-writing the 383 | * slickwrap plugin to just be a no-op. 384 | */ 385 | var testCanvas = document.createElement("CANVAS"); 386 | if(testCanvas.getContext == null){ 387 | $.fn.slickWrap = function(nodes){ 388 | return this; 389 | }; 390 | } 391 | })(jQuery); 392 | 393 | -------------------------------------------------------------------------------- /test/escalator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonwyatt/jQSlickWrap/2c0d8fb34807bea9f5e9e45a4f8f04a5238237b0/test/escalator.png -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jQSlickWrap - Slick text wrapping for jQuery 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 21 | 22 | 32 | 33 | 34 |

jQSlickWrap

35 |

Left-Floated Without sourceImage

36 |
37 | 38 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc justo massa, mattis in imperdiet in, pellentesque sit amet elit. Fusce vitae pulvinar nisi. Ut sed justo nec est congue cursus vestibulum eu dolor. Donec at mauris felis, sit amet ultrices odio. Aliquam erat volutpat. Nullam faucibus metus eu elit luctus sed malesuada risus molestie. Mauris nulla quam, tristique at lobortis at, fringilla quis nibh. Ut sapien mauris, imperdiet eget tincidunt semper, consectetur a augue. Donec vitae nibh augue, ut rhoncus elit. Nullam volutpat lorem sed odio lacinia non aliquet erat consequat. In ac libero turpis. In commodo nisl id diam dapibus varius. Sed lobortis ultricies ligula, quis auctor arcu imperdiet eget. Donec vel ipsum dui. In justo purus, molestie sit amet mattis sed, cursus non orci. Nullam ac massa vel tortor scelerisque blandit quis a sapien.

39 |

Morbi ipsum massa, commodo in auctor a, tincidunt et nisl. In hac habitasse platea dictumst. Fusce molestie, leo ut pellentesque posuere, dui nunc dignissim massa, non aliquet felis sem nec nisl. In pellentesque condimentum tellus, sed hendrerit dolor porttitor vel. Nam dapibus urna a sem semper non porttitor nulla varius. Morbi libero metus, dictum vel viverra aliquet, fringilla vitae tellus. Maecenas ac libero mauris. Pellentesque vitae urna erat, lobortis venenatis ipsum. Ut nec gravida arcu. Suspendisse at risus nulla. Vivamus dictum tempus enim at pellentesque. Curabitur ac tellus ligula, non tristique risus. Nunc vitae ipsum nec libero consectetur tincidunt nec eu nisl. Fusce odio nisi, hendrerit nec aliquam in, dignissim et magna. Nullam tempor condimentum ligula vel cursus. Pellentesque ut sapien at ligula pellentesque congue ut ut turpis.

40 |
41 |

Left-Floated With sourceImage

42 |
43 | 44 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc justo massa, mattis in imperdiet in, pellentesque sit amet elit. Fusce vitae pulvinar nisi. Ut sed justo nec est congue cursus vestibulum eu dolor. Donec at mauris felis, sit amet ultrices odio. Aliquam erat volutpat. Nullam faucibus metus eu elit luctus sed malesuada risus molestie. Mauris nulla quam, tristique at lobortis at, fringilla quis nibh. Ut sapien mauris, imperdiet eget tincidunt semper, consectetur a augue. Donec vitae nibh augue, ut rhoncus elit. Nullam volutpat lorem sed odio lacinia non aliquet erat consequat. In ac libero turpis. In commodo nisl id diam dapibus varius. Sed lobortis ultricies ligula, quis auctor arcu imperdiet eget. Donec vel ipsum dui. In justo purus, molestie sit amet mattis sed, cursus non orci. Nullam ac massa vel tortor scelerisque blandit quis a sapien.

45 |

Morbi ipsum massa, commodo in auctor a, tincidunt et nisl. In hac habitasse platea dictumst. Fusce molestie, leo ut pellentesque posuere, dui nunc dignissim massa, non aliquet felis sem nec nisl. In pellentesque condimentum tellus, sed hendrerit dolor porttitor vel. Nam dapibus urna a sem semper non porttitor nulla varius. Morbi libero metus, dictum vel viverra aliquet, fringilla vitae tellus. Maecenas ac libero mauris. Pellentesque vitae urna erat, lobortis venenatis ipsum. Ut nec gravida arcu. Suspendisse at risus nulla. Vivamus dictum tempus enim at pellentesque. Curabitur ac tellus ligula, non tristique risus. Nunc vitae ipsum nec libero consectetur tincidunt nec eu nisl. Fusce odio nisi, hendrerit nec aliquam in, dignissim et magna. Nullam tempor condimentum ligula vel cursus. Pellentesque ut sapien at ligula pellentesque congue ut ut turpis.

46 |
47 |

Right-Floated With sourceImage

48 |
49 | 50 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc justo massa, mattis in imperdiet in, pellentesque sit amet elit. Fusce vitae pulvinar nisi. Ut sed justo nec est congue cursus vestibulum eu dolor. Donec at mauris felis, sit amet ultrices odio. Aliquam erat volutpat. Nullam faucibus metus eu elit luctus sed malesuada risus molestie. Mauris nulla quam, tristique at lobortis at, fringilla quis nibh. Ut sapien mauris, imperdiet eget tincidunt semper, consectetur a augue. Donec vitae nibh augue, ut rhoncus elit. Nullam volutpat lorem sed odio lacinia non aliquet erat consequat. In ac libero turpis. In commodo nisl id diam dapibus varius. Sed lobortis ultricies ligula, quis auctor arcu imperdiet eget. Donec vel ipsum dui. In justo purus, molestie sit amet mattis sed, cursus non orci. Nullam ac massa vel tortor scelerisque blandit quis a sapien.

51 |

Morbi ipsum massa, commodo in auctor a, tincidunt et nisl. In hac habitasse platea dictumst. Fusce molestie, leo ut pellentesque posuere, dui nunc dignissim massa, non aliquet felis sem nec nisl. In pellentesque condimentum tellus, sed hendrerit dolor porttitor vel. Nam dapibus urna a sem semper non porttitor nulla varius. Morbi libero metus, dictum vel viverra aliquet, fringilla vitae tellus. Maecenas ac libero mauris. Pellentesque vitae urna erat, lobortis venenatis ipsum. Ut nec gravida arcu. Suspendisse at risus nulla. Vivamus dictum tempus enim at pellentesque. Curabitur ac tellus ligula, non tristique risus. Nunc vitae ipsum nec libero consectetur tincidunt nec eu nisl. Fusce odio nisi, hendrerit nec aliquam in, dignissim et magna. Nullam tempor condimentum ligula vel cursus. Pellentesque ut sapien at ligula pellentesque congue ut ut turpis.

52 |
53 | 54 | 55 | --------------------------------------------------------------------------------