lazyload.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. /*!
  2. * author:jieyou
  3. * see https://github.com/jieyou/lazyload
  4. * part of the code fork from tuupola's https://github.com/tuupola/jquery_lazyload
  5. */
  6. ;(function(factory){
  7. if(typeof define === 'function' && define.amd){ // AMD
  8. // you may need to change `define([------>'jquery'<------], factory)`
  9. // if you use zepto, change it rely name, such as `define(['zepto'], factory)`
  10. // if your jquery|zepto lib is in other path, change it such as `define(['lib\jquery.min'], factory)`
  11. define(['jquery'], factory)
  12. }else{ // Global
  13. factory(window.jQuery || window.Zepto)
  14. }
  15. })(function($,undefined){
  16. var w = window,
  17. $window = $(w),
  18. defaultOptions = {
  19. threshold : 0,
  20. failure_limit : 0,
  21. event : 'scroll',
  22. effect : 'show',
  23. effect_params : null,
  24. container : w,
  25. data_attribute : 'original',
  26. data_srcset_attribute : 'original-srcset',
  27. skip_invisible : true,
  28. appear : emptyFn,
  29. load : emptyFn,
  30. vertical_only : false,
  31. minimum_interval : 300,
  32. use_minimum_interval_in_ios : false,
  33. url_rewriter_fn : emptyFn,
  34. no_fake_img_loader : false,
  35. placeholder_data_img : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC',
  36. // for IE6\7 that does not support data image
  37. placeholder_real_img : 'http://ditu.baidu.cn/yyfm/lazyload/0.0.1/img/placeholder.png'
  38. // todo : 将某些属性用global来配置,而不是每次在$(selector).lazyload({})内配置
  39. },
  40. isIOS = (/(?:iphone|ipod|ipad).*os/gi).test(navigator.appVersion),
  41. isIOS5 = isIOS && (/(?:iphone|ipod|ipad).*os 5/gi).test(navigator.appVersion),
  42. type // function
  43. function emptyFn(){}
  44. type = (function(){
  45. var object_prototype_toString = Object.prototype.toString
  46. return function(obj){
  47. // todo: compare the speeds of replace string twice or replace a regExp
  48. return object_prototype_toString.call(obj).replace('[object ','').replace(']','')
  49. }
  50. })()
  51. function belowthefold($element, options){
  52. var fold
  53. if(options._$container == $window){
  54. fold = ('innerHeight' in w ? w.innerHeight : $window.height()) + $window.scrollTop()
  55. }else{
  56. fold = options._$container.offset().top + options._$container.height()
  57. }
  58. return fold <= $element.offset().top - options.threshold
  59. }
  60. function rightoffold($element, options){
  61. var fold
  62. if(options._$container == $window){
  63. // Zepto do not support `$window.scrollLeft()` yet.
  64. fold = $window.width() + ($.fn.scrollLeft?$window.scrollLeft():w.pageXOffset)
  65. }else{
  66. fold = options._$container.offset().left + options._$container.width()
  67. }
  68. return fold <= $element.offset().left - options.threshold
  69. }
  70. function abovethetop($element, options){
  71. var fold
  72. if(options._$container == $window){
  73. fold = $window.scrollTop()
  74. }else{
  75. fold = options._$container.offset().top
  76. }
  77. // console.log('abovethetop fold '+ fold)
  78. // console.log('abovethetop $element.height() '+ $element.height())
  79. return fold >= $element.offset().top + options.threshold + $element.height()
  80. }
  81. function leftofbegin($element, options){
  82. var fold
  83. if(options._$container == $window){
  84. // Zepto do not support `$window.scrollLeft()` yet.
  85. fold = $.fn.scrollLeft?$window.scrollLeft():w.pageXOffset
  86. }else{
  87. fold = options._$container.offset().left
  88. }
  89. return fold >= $element.offset().left + options.threshold + $element.width()
  90. }
  91. function checkAppear($elements, options){
  92. var counter = 0
  93. $elements.each(function(i,e){
  94. var $element = $elements.eq(i)
  95. if(options.skip_invisible &&
  96. // Support zepto
  97. !($element.width() || $element.height()) && $element.css("display") !== "none"){
  98. return
  99. }
  100. function appear(){
  101. $element.trigger('_lazyload_appear')
  102. // if we found an image we'll load, reset the counter
  103. counter = 0
  104. }
  105. // If vertical_only is set to true, only check the vertical to decide appear or not
  106. // In most situations, page can only scroll vertically, set vertical_only to true will improve performance
  107. if(options.vertical_only){
  108. if(abovethetop($element, options)){
  109. // Nothing.
  110. }else if(!belowthefold($element, options)){
  111. appear()
  112. }else{
  113. if(++counter > options.failure_limit){
  114. return false
  115. }
  116. }
  117. }else{
  118. if(abovethetop($element, options) || leftofbegin($element, options)){
  119. // Nothing.
  120. }else if(!belowthefold($element, options) && !rightoffold($element, options)){
  121. appear()
  122. }else{
  123. if(++counter > options.failure_limit){
  124. return false
  125. }
  126. }
  127. }
  128. })
  129. }
  130. // Remove image from array so it is not looped next time.
  131. function getUnloadElements($elements){
  132. return $elements.filter(function(i,e){
  133. return !$elements.eq(i)._lazyload_loadStarted
  134. })
  135. }
  136. if(!$.fn.hasOwnProperty('lazyload')){
  137. $.fn.lazyload = function(options){
  138. var $elements = this,
  139. isScrollEvent,
  140. isScrollTypeEvent,
  141. scrollTimer = null,
  142. hasMinimumInterval
  143. if(!$.isPlainObject(options)){
  144. options = {}
  145. }
  146. $.each(defaultOptions,function(k,v){
  147. if($.inArray(k,['threshold','failure_limit','minimum_interval']) != -1){ // these params can be a string
  148. if(type(options[k]) == 'String'){
  149. options[k] = parseInt(options[k],10)
  150. }else{
  151. options[k] = v
  152. }
  153. }else if(k == 'container'){ // options.container can be a seletor string \ dom \ jQuery object
  154. if(options.hasOwnProperty(k)){
  155. if(options[k] == w || options[k] == document){
  156. options._$container = $window
  157. }else{
  158. options._$container = $(options[k])
  159. }
  160. }else{
  161. options._$container = $window
  162. }
  163. delete options.container
  164. }else if(defaultOptions.hasOwnProperty(k) && (!options.hasOwnProperty(k) || (type(options[k]) != type(defaultOptions[k])))){
  165. options[k] = v
  166. }
  167. })
  168. isScrollEvent = options.event == 'scroll'
  169. // isScrollTypeEvent. cantains custom scrollEvent . Such as 'scrollstart' & 'scrollstop'
  170. isScrollTypeEvent = isScrollEvent || options.event == 'scrollstart' || options.event == 'scrollstop'
  171. $elements.each(function(i,e){
  172. var element = this,
  173. $element = $elements.eq(i),
  174. placeholderSrc = $element.attr('src'),
  175. originalSrcInAttr = $element.attr('data-'+options.data_attribute), // `data-original` attribute value
  176. originalSrc = options.url_rewriter_fn == emptyFn?
  177. originalSrcInAttr:
  178. options.url_rewriter_fn.call(element,$element,originalSrcInAttr),
  179. originalSrcset = $element.attr('data-'+options.data_srcset_attribute),
  180. isImg = $element.is('img')
  181. if($element._lazyload_loadStarted == true || placeholderSrc == originalSrc){
  182. $element._lazyload_loadStarted = true
  183. $elements = getUnloadElements($elements)
  184. return
  185. }
  186. $element._lazyload_loadStarted = false
  187. // If element is an img and no src attribute given, use placeholder.
  188. if(isImg && !placeholderSrc){
  189. // For browsers that do not support data image.
  190. $element.one('error',function(){ // `on` -> `one` : IE6 triggered twice error event sometimes
  191. $element.attr('src',options.placeholder_real_img)
  192. }).attr('src',options.placeholder_data_img)
  193. }
  194. // When appear is triggered load original image.
  195. $element.one('_lazyload_appear',function(){
  196. var effectParamsIsArray = $.isArray(options.effect_params),
  197. effectIsNotImmediacyShow
  198. function loadFunc(){
  199. // In most situations, the effect is immediacy show, at this time there is no need to hide element first
  200. // Hide this element may cause css reflow, call it as less as possible
  201. if(effectIsNotImmediacyShow){
  202. // todo: opacity:0 for fadeIn effect
  203. $element.hide()
  204. }
  205. if(isImg){
  206. // attr srcset first
  207. if(originalSrcset){
  208. $element.attr('srcset', originalSrcset)
  209. }
  210. if(originalSrc){
  211. $element.attr('src', originalSrc)
  212. }
  213. }else{
  214. $element.css('background-image','url("' + originalSrc + '")')
  215. }
  216. if(effectIsNotImmediacyShow){
  217. $element[options.effect].apply($element,effectParamsIsArray?options.effect_params:[])
  218. }
  219. $elements = getUnloadElements($elements)
  220. }
  221. if(!$element._lazyload_loadStarted){
  222. effectIsNotImmediacyShow = (options.effect != 'show' && $.fn[options.effect] && (!options.effect_params || (effectParamsIsArray && options.effect_params.length == 0)))
  223. if(options.appear != emptyFn){
  224. options.appear.call(element, $elements.length, options)
  225. }
  226. $element._lazyload_loadStarted = true
  227. if(options.no_fake_img_loader || originalSrcset){
  228. if(options.load != emptyFn){
  229. $element.one('load',function(){
  230. options.load.call(element, $elements.length, options)
  231. })
  232. }
  233. loadFunc()
  234. }else{
  235. $('<img />').one('load', function(){ // `on` -> `one` : IE6 triggered twice load event sometimes
  236. loadFunc()
  237. if(options.load != emptyFn){
  238. options.load.call(element, $elements.length, options)
  239. }
  240. }).attr('src',originalSrc)
  241. }
  242. }
  243. })
  244. // When wanted event is triggered load original image
  245. // by triggering appear.
  246. if (!isScrollTypeEvent){
  247. $element.on(options.event, function(){
  248. if (!$element._lazyload_loadStarted){
  249. $element.trigger('_lazyload_appear')
  250. }
  251. })
  252. }
  253. })
  254. // Fire one scroll event per scroll. Not one scroll event per image.
  255. if(isScrollTypeEvent){
  256. hasMinimumInterval = options.minimum_interval != 0
  257. options._$container.on(options.event, function(){
  258. // desktop and Android device triggered many times `scroll` event in once user scrolling
  259. if(isScrollEvent && hasMinimumInterval && (!isIOS || options.use_minimum_interval_in_ios)){
  260. if(!scrollTimer){
  261. scrollTimer = setTimeout(function(){
  262. checkAppear($elements, options)
  263. scrollTimer = null
  264. },options.minimum_interval) // only check once in 300ms
  265. }
  266. }else{
  267. return checkAppear($elements, options)
  268. }
  269. })
  270. }
  271. // Check if something appears when window is resized.
  272. // Force initial check if images should appear when window onload.
  273. $window.on('resize load', function(){
  274. checkAppear($elements, options)
  275. })
  276. // With IOS5 force loading images when navigating with back button.
  277. // Non optimal workaround.
  278. if(isIOS5){
  279. $window.on('pageshow', function(e){
  280. if(e.originalEvent && e.originalEvent.persisted){
  281. $elements.trigger('_lazyload_appear')
  282. }
  283. })
  284. }
  285. // Force initial check if images should appear.
  286. $(function(){
  287. checkAppear($elements, options)
  288. })
  289. return this
  290. }
  291. }
  292. })