[Owncloud] [core files] [PATCH] : Adding Lazy Image Icons

R Veach rveach02 at gmail.com
Wed Jan 8 21:38:47 UTC 2014


I originally submitted a feature request for changing always loading file
icon images to lazy loading them to save on bandwidth when opening a folder
with many pictures inside it.
The original issue can be read here at
https://github.com/owncloud/core/issues/6591 and all my reasoning for the
need of this addition.

I have made small changes to the owncloud's core app "files" that achieves
this functionality and am submitting it here so hopefully it can be
incorporated.
The main addition is the JQuery Lazy Load library that I found at
http://www.appelsiini.net/projects/lazyload , which can be used, as is,
with no modifications. The library is released under the MIT License, which
I believe it compatible with owncloud's APGL. Correct me if I am wrong.
The main changes to the file listing is replacing the background image
style for the icon and putting it into a custom attribute that Lazy Load
recognizes and works with.
The only other change is the call to the Lazy Load function in JS to
monitor the user scrolling and position in the document.

I have tested this with owncloud 6.0.0a and found no issues in IE8 and
FF26. The images that are not in view don't load when opening a directory
(verified with firebug's net panel), the images are loaded correctly when
scrolled into view, and cached images still show up rather instantly. I
didn't see any problems with applying the changes to the master in git.

I have attached the patch using "diff -Naur".
"owncloudFilesOld" is the original core app files directory.
"owncloudFilesLazy" is the core app files directory with my modifications
for adding lazy load. The core app files directory is located in
"owncloud\apps\files".
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.kde.org/pipermail/owncloud/attachments/20140108/415ed6b9/attachment.html>
-------------- next part --------------
?diff -Naur owncloudFilesOld/index.php owncloudFilesLazy/index.php
--- owncloudFilesOld/index.php	2013-12-14 14:47:38.000000000 -0500
+++ owncloudFilesLazy/index.php	2014-01-07 19:00:35.087288328 -0500
@@ -31,6 +31,7 @@
 OCP\Util::addscript('files', 'jquery.iframe-transport');
 OCP\Util::addscript('files', 'jquery.fileupload');
 OCP\Util::addscript('files', 'jquery-visibility');
+OCP\Util::addscript('files', 'jquery.lazyload');
 OCP\Util::addscript('files', 'filelist');
 
 OCP\App::setActiveNavigationEntry('files_index');
diff -Naur owncloudFilesOld/js/filelist.js owncloudFilesLazy/js/filelist.js
--- owncloudFilesOld/js/filelist.js	2013-12-14 14:47:38.000000000 -0500
+++ owncloudFilesLazy/js/filelist.js	2014-01-07 19:34:19.259285123 -0500
@@ -20,6 +20,7 @@
 			Files.setupDragAndDrop();
 		}
 		FileList.updateFileSummary();
+		$(".lazy").lazyload();
 		$fileList.trigger(jQuery.Event("updated"));
 	},
 	createRow:function(type, name, iconurl, linktarget, size, lastModified, permissions) {
@@ -771,6 +772,8 @@
 $(document).ready(function() {
 	var isPublic = !!$('#isPublic').val();
 
+	$(".lazy").lazyload();
+
 	// handle upload events
 	var file_upload_start = $('#file_upload_start');
 
diff -Naur owncloudFilesOld/js/jquery.lazyload.js owncloudFilesLazy/js/jquery.lazyload.js
--- owncloudFilesOld/js/jquery.lazyload.js	1969-12-31 19:00:00.000000000 -0500
+++ owncloudFilesLazy/js/jquery.lazyload.js	2014-01-07 18:53:12.271289030 -0500
@@ -0,0 +1,242 @@
+/*
+ * Lazy Load - jQuery plugin for lazy loading images
+ *
+ * Copyright (c) 2007-2013 Mika Tuupola
+ *
+ * Licensed under the MIT license:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *
+ * Project home:
+ *   http://www.appelsiini.net/projects/lazyload
+ *
+ * Version:  1.9.1
+ *
+ */
+
+(function($, window, document, undefined) {
+    var $window = $(window);
+
+    $.fn.lazyload = function(options) {
+        var elements = this;
+        var $container;
+        var settings = {
+            threshold       : 0,
+            failure_limit   : 0,
+            event           : "scroll",
+            effect          : "show",
+            container       : window,
+            data_attribute  : "original",
+            skip_invisible  : true,
+            appear          : null,
+            load            : null,
+            placeholder     : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC"
+        };
+
+        function update() {
+            var counter = 0;
+
+            elements.each(function() {
+                var $this = $(this);
+                if (settings.skip_invisible && !$this.is(":visible")) {
+                    return;
+                }
+                if ($.abovethetop(this, settings) ||
+                    $.leftofbegin(this, settings)) {
+                        /* Nothing. */
+                } else if (!$.belowthefold(this, settings) &&
+                    !$.rightoffold(this, settings)) {
+                        $this.trigger("appear");
+                        /* if we found an image we'll load, reset the counter */
+                        counter = 0;
+                } else {
+                    if (++counter > settings.failure_limit) {
+                        return false;
+                    }
+                }
+            });
+
+        }
+
+        if(options) {
+            /* Maintain BC for a couple of versions. */
+            if (undefined !== options.failurelimit) {
+                options.failure_limit = options.failurelimit;
+                delete options.failurelimit;
+            }
+            if (undefined !== options.effectspeed) {
+                options.effect_speed = options.effectspeed;
+                delete options.effectspeed;
+            }
+
+            $.extend(settings, options);
+        }
+
+        /* Cache container as jQuery as object. */
+        $container = (settings.container === undefined ||
+                      settings.container === window) ? $window : $(settings.container);
+
+        /* Fire one scroll event per scroll. Not one scroll event per image. */
+        if (0 === settings.event.indexOf("scroll")) {
+            $container.bind(settings.event, function() {
+                return update();
+            });
+        }
+
+        this.each(function() {
+            var self = this;
+            var $self = $(self);
+
+            self.loaded = false;
+
+            /* If no src attribute given use data:uri. */
+            if ($self.attr("src") === undefined || $self.attr("src") === false) {
+                if ($self.is("img")) {
+                    $self.attr("src", settings.placeholder);
+                }
+            }
+
+            /* When appear is triggered load original image. */
+            $self.one("appear", function() {
+                if (!this.loaded) {
+                    if (settings.appear) {
+                        var elements_left = elements.length;
+                        settings.appear.call(self, elements_left, settings);
+                    }
+                    $("<img />")
+                        .bind("load", function() {
+
+                            var original = $self.attr("data-" + settings.data_attribute);
+                            $self.hide();
+                            if ($self.is("img")) {
+                                $self.attr("src", original);
+                            } else {
+                                $self.css("background-image", "url('" + original + "')");
+                            }
+                            $self[settings.effect](settings.effect_speed);
+
+                            self.loaded = true;
+
+                            /* Remove image from array so it is not looped next time. */
+                            var temp = $.grep(elements, function(element) {
+                                return !element.loaded;
+                            });
+                            elements = $(temp);
+
+                            if (settings.load) {
+                                var elements_left = elements.length;
+                                settings.load.call(self, elements_left, settings);
+                            }
+                        })
+                        .attr("src", $self.attr("data-" + settings.data_attribute));
+                }
+            });
+
+            /* When wanted event is triggered load original image */
+            /* by triggering appear.                              */
+            if (0 !== settings.event.indexOf("scroll")) {
+                $self.bind(settings.event, function() {
+                    if (!self.loaded) {
+                        $self.trigger("appear");
+                    }
+                });
+            }
+        });
+
+        /* Check if something appears when window is resized. */
+        $window.bind("resize", function() {
+            update();
+        });
+
+        /* With IOS5 force loading images when navigating with back button. */
+        /* Non optimal workaround. */
+        if ((/(?:iphone|ipod|ipad).*os 5/gi).test(navigator.appVersion)) {
+            $window.bind("pageshow", function(event) {
+                if (event.originalEvent && event.originalEvent.persisted) {
+                    elements.each(function() {
+                        $(this).trigger("appear");
+                    });
+                }
+            });
+        }
+
+        /* Force initial check if images should appear. */
+        $(document).ready(function() {
+            update();
+        });
+
+        return this;
+    };
+
+    /* Convenience methods in jQuery namespace.           */
+    /* Use as  $.belowthefold(element, {threshold : 100, container : window}) */
+
+    $.belowthefold = function(element, settings) {
+        var fold;
+
+        if (settings.container === undefined || settings.container === window) {
+            fold = (window.innerHeight ? window.innerHeight : $window.height()) + $window.scrollTop();
+        } else {
+            fold = $(settings.container).offset().top + $(settings.container).height();
+        }
+
+        return fold <= $(element).offset().top - settings.threshold;
+    };
+
+    $.rightoffold = function(element, settings) {
+        var fold;
+
+        if (settings.container === undefined || settings.container === window) {
+            fold = $window.width() + $window.scrollLeft();
+        } else {
+            fold = $(settings.container).offset().left + $(settings.container).width();
+        }
+
+        return fold <= $(element).offset().left - settings.threshold;
+    };
+
+    $.abovethetop = function(element, settings) {
+        var fold;
+
+        if (settings.container === undefined || settings.container === window) {
+            fold = $window.scrollTop();
+        } else {
+            fold = $(settings.container).offset().top;
+        }
+
+        return fold >= $(element).offset().top + settings.threshold  + $(element).height();
+    };
+
+    $.leftofbegin = function(element, settings) {
+        var fold;
+
+        if (settings.container === undefined || settings.container === window) {
+            fold = $window.scrollLeft();
+        } else {
+            fold = $(settings.container).offset().left;
+        }
+
+        return fold >= $(element).offset().left + settings.threshold + $(element).width();
+    };
+
+    $.inviewport = function(element, settings) {
+         return !$.rightoffold(element, settings) && !$.leftofbegin(element, settings) &&
+                !$.belowthefold(element, settings) && !$.abovethetop(element, settings);
+     };
+
+    /* Custom selectors for your convenience.   */
+    /* Use as $("img:below-the-fold").something() or */
+    /* $("img").filter(":below-the-fold").something() which is faster */
+
+    $.extend($.expr[":"], {
+        "below-the-fold" : function(a) { return $.belowthefold(a, {threshold : 0}); },
+        "above-the-top"  : function(a) { return !$.belowthefold(a, {threshold : 0}); },
+        "right-of-screen": function(a) { return $.rightoffold(a, {threshold : 0}); },
+        "left-of-screen" : function(a) { return !$.rightoffold(a, {threshold : 0}); },
+        "in-viewport"    : function(a) { return $.inviewport(a, {threshold : 0}); },
+        /* Maintain BC for couple of versions. */
+        "above-the-fold" : function(a) { return !$.belowthefold(a, {threshold : 0}); },
+        "right-of-fold"  : function(a) { return $.rightoffold(a, {threshold : 0}); },
+        "left-of-fold"   : function(a) { return !$.rightoffold(a, {threshold : 0}); }
+    });
+
+})(jQuery, window, document);
diff -Naur owncloudFilesOld/templates/part.list.php owncloudFilesLazy/templates/part.list.php
--- owncloudFilesOld/templates/part.list.php	2013-12-14 14:47:38.000000000 -0500
+++ owncloudFilesLazy/templates/part.list.php	2014-01-07 19:11:41.523287272 -0500
@@ -19,11 +19,11 @@
 		data-etag="<?php p($file['etag']);?>"
 		data-permissions="<?php p($file['permissions']); ?>">
 		<?php if($file['isPreviewAvailable']): ?>
-		<td class="filename svg preview-icon"
+		<td class="filename svg preview-icon lazy"
 		<?php else: ?>
-		<td class="filename svg"
+		<td class="filename svg lazy"
 		<?php endif; ?>
-		    style="background-image:url(<?php print_unescaped($file['icon']); ?>)"
+		    data-original="<?php print_unescaped($file['icon']); ?>"
 			>
 		<?php if(!isset($_['readonly']) || !$_['readonly']): ?>
 			<input id="select-<?php p($file['fileid']); ?>" type="checkbox" />


More information about the Owncloud mailing list