adaptive-backgrounds.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. /* jshint debug: true, expr: true */
  2. ;
  3. (function ($) {
  4. /* Constants & defaults. */
  5. var DATA_COLOR = 'data-ab-color';
  6. var DATA_PARENT = 'data-ab-parent';
  7. var DATA_CSS_BG = 'data-ab-css-background';
  8. var EVENT_CF = 'ab-color-found';
  9. var BLEND = 'blend';
  10. var DEFAULTS = {
  11. selector: '[data-adaptive-background]',
  12. parent: null,
  13. exclude: ['rgb(0,0,0)', 'rgb(255,255,255)'],
  14. shadeVariation: false,
  15. shadePercentage: 0,
  16. shadeColors: {
  17. light: 'rgb(255,255,255)',
  18. dark: 'rgb(0,0,0)'
  19. },
  20. normalizeTextColor: false,
  21. normalizedTextColors: {
  22. light: "#fff",
  23. dark: "#000"
  24. },
  25. lumaClasses: {
  26. light: "ab-light",
  27. dark: "ab-dark"
  28. },
  29. transparent: null
  30. };
  31. // Include RGBaster - https://github.com/briangonzalez/rgbaster.js
  32. /* jshint ignore:start */
  33. !function(n,t){"use strict";var e=function(n,t){var e=document.createElement("canvas");return e.setAttribute("width",n),e.setAttribute("height",t),e.getContext("2d")},r=function(n,t){var r=new Image,i=n.src||n;"data:"!==i.substring(0,5)&&(r.crossOrigin="Anonymous"),r.onload=function(){var n=e(r.width,r.height);n.drawImage(r,0,0);var i=n.getImageData(0,0,r.width,r.height);t&&t(i.data)},r.src=i},i=function(n){return["rgb(",n,")"].join("")},a=function(n){var t=[];for(var e in n)t.push(o(e,n[e]));return t.sort(function(n,t){return t.count-n.count}),t},u=function(n,t){if(n.length>t)return n.slice(0,t);for(var e=n.length-1;t-1>e;e++)n.push(o("0,0,0",0));return n},o=function(n,t){return{name:i(n),count:t}},c=10,s={};s.colors=function(n,e){e=e||{};var o=e.exclude||[],s=e.paletteSize||c;r(n,function(n){for(var r={},c="",f=[],d=0;d<n.length;d+=4)f[0]=n[d],f[1]=n[d+1],f[2]=n[d+2],c=f.join(","),-1===f.indexOf(t)&&0!==n[d+3]&&-1===o.indexOf(i(c))&&(r[c]=c in r?r[c]+1:1);if(e.success){var g=u(a(r),s+1);e.success({dominant:g[0].name,secondary:g[1].name,palette:g.map(function(n){return n.name}).slice(1)})}})},n.RGBaster=n.RGBaster||s}(window);
  34. function shadeRGBColor(color, percent){var f=color.split(","),t=percent<0?0:255,p=percent<0?percent*-1:percent,R=parseInt(f[0].slice(4)),G=parseInt(f[1]),B=parseInt(f[2]);return "rgb("+(Math.round((t-R)*p)+R)+","+(Math.round((t-G)*p)+G)+","+(Math.round((t-B)*p)+B)+")";}
  35. function blendRGBColors(c0, c1, p){var f=c0.split(","),t=c1.split(","),R=parseInt(f[0].slice(4)),G=parseInt(f[1]),B=parseInt(f[2]);return "rgb("+(Math.round((parseInt(t[0].slice(4))-R)*p)+R)+","+(Math.round((parseInt(t[1])-G)*p)+G)+","+(Math.round((parseInt(t[2])-B)*p)+B)+")";}
  36. /* jshint ignore:end */
  37. /*
  38. Our main function declaration.
  39. */
  40. $.adaptiveBackground = {
  41. run: function (options) {
  42. var opts = $.extend({}, DEFAULTS, options);
  43. /* Loop over each element, waiting for it to load
  44. then finding its color, and triggering the
  45. color found event when color has been found.
  46. */
  47. $(opts.selector).each(function (index, el) {
  48. var $this = $(this);
  49. /* Small helper functions which applies
  50. colors, attrs, triggers events, etc.
  51. */
  52. var handleColors = function () {
  53. if ($this[0].tagName == 'PICTURE') {
  54. var images = $this[0].children;
  55. for (var image in images) {
  56. if (images[image].tagName == 'IMG') {
  57. var img = images[image];
  58. break;
  59. }
  60. };
  61. if (img.currentSrc) {
  62. img = img.currentSrc;
  63. };
  64. } else {
  65. var img = useCSSBackground() ? getCSSBackground() : $this[0];
  66. }
  67. RGBaster.colors(img, {
  68. paletteSize: 20,
  69. exclude: opts.exclude,
  70. success: function (colors) {
  71. $this.attr(DATA_COLOR, colors.dominant);
  72. $this.trigger(EVENT_CF, {
  73. color: colors.dominant,
  74. palette: colors.palette
  75. });
  76. }
  77. });
  78. };
  79. // Helper function to calculate yiq - http://en.wikipedia.org/wiki/YIQ
  80. var getYIQ = function (color) {
  81. var rgb = color.match(/\d+/g);
  82. return ((rgb[0] * 299) + (rgb[1] * 587) + (rgb[2] * 114)) / 1000;
  83. };
  84. var useCSSBackground = function () {
  85. var attr = $this.attr(DATA_CSS_BG);
  86. return (typeof attr !== typeof undefined && attr !== false);
  87. };
  88. var getCSSBackground = function () {
  89. var str = $this.css('background-image');
  90. var regex = /\(([^)]+)\)/;
  91. var match = regex.exec(str)[1].replace(/"/g, '')
  92. return match;
  93. };
  94. var getShadeAdjustment = function (color) {
  95. if (opts.shadeVariation == true) {
  96. return getYIQ(color) <= 128 ? shadeRGBColor(color, opts.shadePercentage) : shadeRGBColor(color, -(opts.shadePercentage), opts.shadePercentage);
  97. } else if (opts.shadeVariation == BLEND) {
  98. return getYIQ(color) >= 128 ? blendRGBColors(color, opts.shadeColors.dark, opts.shadePercentage) : blendRGBColors(color, opts.shadeColors.light, opts.shadePercentage);
  99. }
  100. };
  101. /* Subscribe to our color-found event. */
  102. $this.on(EVENT_CF, function (ev, data) {
  103. // Try to find the parent.
  104. var $parent;
  105. if (opts.parent && $this.parents(opts.parent).length) {
  106. $parent = $this.parents(opts.parent);
  107. } else if ($this.attr(DATA_PARENT) && $this.parents($this.attr(DATA_PARENT)).length) {
  108. $parent = $this.parents($this.attr(DATA_PARENT));
  109. } else if (useCSSBackground()) {
  110. $parent = $this;
  111. } else if (opts.parent) {
  112. $parent = $this.parents(opts.parent);
  113. } else {
  114. $parent = $this.parent();
  115. }
  116. if (!!opts.shadeVariation)
  117. data.color = getShadeAdjustment(data.color);
  118. if ($.isNumeric(opts.transparent) && opts.transparent != null && opts.transparent >= 0.01 && opts.transparent <= 0.99) {
  119. var dominantColor = data.color;
  120. var rgbToRgba = dominantColor.replace("rgb", "rgba");
  121. var transparentColor = rgbToRgba.replace(")", ", " + opts.transparent + ")");
  122. $parent.css({
  123. backgroundColor: transparentColor
  124. });
  125. } else {
  126. $parent.css({
  127. backgroundColor: data.color
  128. });
  129. }
  130. var getNormalizedTextColor = function (color) {
  131. return getYIQ(color) >= 128 ? opts.normalizedTextColors.dark : opts.normalizedTextColors.light;
  132. };
  133. var getLumaClass = function (color) {
  134. return getYIQ(color) <= 128 ? opts.lumaClasses.dark : opts.lumaClasses.light;
  135. };
  136. // Normalize the text color based on luminance.
  137. if (opts.normalizeTextColor)
  138. $parent.css({
  139. color: getNormalizedTextColor(data.color)
  140. });
  141. // Add a class based on luminance.
  142. $parent.addClass(getLumaClass(data.color))
  143. .attr('data-ab-yaq', getYIQ(data.color));
  144. opts.success && opts.success($this, data);
  145. });
  146. /* Handle the colors. */
  147. handleColors();
  148. });
  149. }
  150. };
  151. })(jQuery);