From 5eb980a8d305779db3730ec32c37efc37c620277 Mon Sep 17 00:00:00 2001 From: sapierens Date: Mon, 1 Jan 2018 16:22:51 +0200 Subject: [PATCH 1/4] Added Rect.getIntersectionWith. --- src/rect.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/rect.ts b/src/rect.ts index b360b9c..1bc75ba 100644 --- a/src/rect.ts +++ b/src/rect.ts @@ -33,4 +33,13 @@ export class Rect { (rect.top < this.bottom) && (this.top < rect.bottom); } + + getIntersectionWith(rect: Rect): Rect { + const left = Math.max(this.left, rect.left); + const top = Math.max(this.top, rect.top); + const right = Math.min(this.right, rect.right); + const bottom = Math.min(this.bottom, rect.bottom); + + return new Rect(left, top, right, bottom); + } } From 26fc5f03a80ce5fcdd4cd81f010288ed43def169 Mon Sep 17 00:00:00 2001 From: sapierens Date: Mon, 1 Jan 2018 17:54:51 +0200 Subject: [PATCH 2/4] Improved isVisible function with scroll containers. --- src/lazyload-image.directive.ts | 23 ++++++++++++----------- src/lazyload-image.ts | 20 +++++++++++++++----- test/lazyload-image.test.ts | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 16 deletions(-) diff --git a/src/lazyload-image.directive.ts b/src/lazyload-image.directive.ts index 6bb51d5..a4452a9 100644 --- a/src/lazyload-image.directive.ts +++ b/src/lazyload-image.directive.ts @@ -18,13 +18,13 @@ import { import { getScrollListener } from './scroll-listener'; import { lazyLoadImage } from './lazyload-image'; -const target = typeof window !== 'undefined' ? window : undefined; +const windowTarget = typeof window !== 'undefined' ? window : undefined; interface LazyLoadImageDirectiveProps { lazyImage: string; defaultImage: string; errorImage: string; - scrollTarget: Object; + scrollTarget: HTMLElement; scrollObservable: Observable; offset: number; useSrcset: boolean; @@ -34,13 +34,13 @@ interface LazyLoadImageDirectiveProps { selector: '[lazyLoad]' }) export class LazyLoadImageDirective implements OnChanges, AfterContentInit, OnDestroy { - @Input('lazyLoad') lazyImage; // The image to be lazy loaded - @Input() defaultImage: string; // The image to be displayed before lazyImage is loaded - @Input() errorImage: string; // The image to be displayed if lazyImage load fails - @Input() scrollTarget = target; // Change the node we should listen for scroll events on, default is window - @Input() scrollObservable; // Pass your own scroll emitter - @Input() offset: number; // The number of px a image should be loaded before it is in view port - @Input() useSrcset: boolean; // Whether srcset attribute should be used instead of src + @Input('lazyLoad') lazyImage; // The image to be lazy loaded + @Input() defaultImage: string; // The image to be displayed before lazyImage is loaded + @Input() errorImage: string; // The image to be displayed if lazyImage load fails + @Input() scrollTarget: HTMLElement; // Scroll container that contains the image and emits scoll events + @Input() scrollObservable; // Pass your own scroll emitter + @Input() offset: number; // The number of px a image should be loaded before it is in view port + @Input() useSrcset: boolean; // Whether srcset attribute should be used instead of src @Output() onLoad: EventEmitter = new EventEmitter(); // Callback when an image is loaded private propertyChanges$: ReplaySubject; private elementRef: ElementRef; @@ -81,7 +81,7 @@ export class LazyLoadImageDirective implements OnChanges, AfterContentInit, OnDe if (this.scrollObservable) { scrollObservable = this.scrollObservable.startWith(''); } else { - scrollObservable = getScrollListener(this.scrollTarget); + scrollObservable = getScrollListener(this.scrollTarget || windowTarget); } this.scrollSubscription = this.propertyChanges$ .debounceTime(10) @@ -92,7 +92,8 @@ export class LazyLoadImageDirective implements OnChanges, AfterContentInit, OnDe props.defaultImage, props.errorImage, props.offset, - props.useSrcset + props.useSrcset, + props.scrollTarget ) )) .subscribe(success => this.onLoad.emit(success)); diff --git a/src/lazyload-image.ts b/src/lazyload-image.ts index 63d1827..349c1cd 100644 --- a/src/lazyload-image.ts +++ b/src/lazyload-image.ts @@ -9,12 +9,22 @@ import { Observable } from 'rxjs/Observable'; import { getScrollListener } from './scroll-listener'; import { Rect } from './rect'; -export function isVisible(element: HTMLElement, threshold = 0, _window = window) { +export function isVisible(element: HTMLElement, threshold = 0, _window: Window, scrollContainer?: HTMLElement) { const elementBounds = Rect.fromElement(element); const windowBounds = Rect.fromWindow(_window); elementBounds.inflate(threshold); - - return elementBounds.intersectsWith(windowBounds); + + if (scrollContainer) { + const scrollContainerBounds = Rect.fromElement(scrollContainer); + if (scrollContainerBounds.intersectsWith(windowBounds)) { + const intersection = scrollContainerBounds.getIntersectionWith(windowBounds); + return elementBounds.intersectsWith(intersection); + } else { + return false; + } + } else { + return elementBounds.intersectsWith(windowBounds); + } } export function isChildOfPicture(element: HTMLImageElement | HTMLDivElement): boolean { @@ -110,7 +120,7 @@ function setLoadedStyle(element: HTMLImageElement | HTMLDivElement) { return element; } -export function lazyLoadImage(element: HTMLImageElement | HTMLDivElement, imagePath: string, defaultImagePath: string, errorImgPath: string, offset: number, useSrcset: boolean = false) { +export function lazyLoadImage(element: HTMLImageElement | HTMLDivElement, imagePath: string, defaultImagePath: string, errorImgPath: string, offset: number, useSrcset: boolean = false, scrollContainer?: HTMLElement) { setImageAndSourcesToDefault(element, defaultImagePath, useSrcset); if (element.className && element.className.includes('ng-lazyloaded')) { element.className = element.className.replace('ng-lazyloaded', ''); @@ -118,7 +128,7 @@ export function lazyLoadImage(element: HTMLImageElement | HTMLDivElement, imageP return (scrollObservable: Observable) => { return scrollObservable - .filter(() => isVisible(element, offset)) + .filter(() => isVisible(element, offset, window, scrollContainer)) .take(1) .mergeMap(() => loadImage(element, imagePath, useSrcset)) .do(() => setImageAndSourcesToLazy(element, imagePath, useSrcset)) diff --git a/test/lazyload-image.test.ts b/test/lazyload-image.test.ts index fd75a6b..4871b86 100644 --- a/test/lazyload-image.test.ts +++ b/test/lazyload-image.test.ts @@ -210,6 +210,38 @@ describe('Lazy load image', () => { is(result, true); }); + + it('Should not be visible when image is horizontally in window\'s view, but not in scroll-container\'s', () => { + const element = generateElement(800, 0, 1200, 1200); + const scrollContainer = generateElement(0, 0, 700, 1200); + const result = isVisible(element, 0, _window, scrollContainer); + + is(result, false); + }); + + it('Should not be visible when image is vertically in window\'s view, but not in scroll-container\'s', () => { + const element = generateElement(0, 800, 1200, 1200); + const scrollContainer = generateElement(0, 0, 1200, 700); + const result = isVisible(element, 0, _window, scrollContainer); + + is(result, false); + }); + + it('Should not be visible when image is not in window\'s view, but is in scroll-container\'s', () => { + const element = generateElement(1400, 0, 1200, 1200); + const scrollContainer = generateElement(1300, 0, 1200, 1200); + const result = isVisible(element, 0, _window, scrollContainer); + + is(result, false); + }); + + it('Should be visible when image is in window\'s and scroll-container\'s view', () => { + const element = generateElement(100, 0, 1200, 1200); + const scrollContainer = generateElement(0, 0, 700, 1200); + const result = isVisible(element, 0, _window, scrollContainer); + + is(result, true); + }); }); }); From 4b9ff3551f76333034e290e6e53191f88e4c4f28 Mon Sep 17 00:00:00 2001 From: sapierens Date: Tue, 2 Jan 2018 00:28:07 +0200 Subject: [PATCH 3/4] Added getIntersectionWith tests. --- test/rect.test.ts | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/test/rect.test.ts b/test/rect.test.ts index e5b334c..e423942 100644 --- a/test/rect.test.ts +++ b/test/rect.test.ts @@ -296,4 +296,51 @@ describe('Rect', () => { is(result, true); }); }); + + describe('getIntersectionWith', () => { + it('Should return a correctly sized Rect if two Rect\'s intersect horizontally', () => { + // Arrange + const rectA = new Rect(0, 0, 20, 20); + const rectB = new Rect(0, 10, 20, 30); + + // Act + const result = rectA.getIntersectionWith(rectB); + + // Assert + is(result.top, 10); + is(result.right, 20); + is(result.bottom, 20); + is(result.left, 0); + }); + + it('Should return a correctly sized Rect if two Rect\'s intersect vertically', () => { + // Arrange + const rectA = new Rect(0, 0, 20, 20); + const rectB = new Rect(10, 0, 30, 20); + + // Act + const result = rectA.getIntersectionWith(rectB); + + // Assert + is(result.top, 0); + is(result.right, 20); + is(result.bottom, 20); + is(result.left, 10); + }); + + it('Should return a correctly sized Rect if two Rect\'s intersect corners', () => { + // Arrange + const rectA = new Rect(0, 0, 20, 20); + const rectB = new Rect(10, 10, 30, 30); + + // Act + const result = rectA.getIntersectionWith(rectB); + + // Assert + is(result.top, 10); + is(result.right, 20); + is(result.bottom, 20); + is(result.left, 10); + }); + }); }); From 5dfba6e674ffb80b2056a0879a472e2da115b15c Mon Sep 17 00:00:00 2001 From: sapierens Date: Tue, 2 Jan 2018 14:32:23 +0200 Subject: [PATCH 4/4] Slight improvement to readability. --- src/lazyload-image.ts | 8 ++------ src/rect.ts | 8 +++++++- test/rect.test.ts | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/lazyload-image.ts b/src/lazyload-image.ts index 349c1cd..ad08180 100644 --- a/src/lazyload-image.ts +++ b/src/lazyload-image.ts @@ -16,12 +16,8 @@ export function isVisible(element: HTMLElement, threshold = 0, _window: Window, if (scrollContainer) { const scrollContainerBounds = Rect.fromElement(scrollContainer); - if (scrollContainerBounds.intersectsWith(windowBounds)) { - const intersection = scrollContainerBounds.getIntersectionWith(windowBounds); - return elementBounds.intersectsWith(intersection); - } else { - return false; - } + const intersection = scrollContainerBounds.getIntersectionWith(windowBounds); + return elementBounds.intersectsWith(intersection); } else { return elementBounds.intersectsWith(windowBounds); } diff --git a/src/rect.ts b/src/rect.ts index 1bc75ba..107751b 100644 --- a/src/rect.ts +++ b/src/rect.ts @@ -1,4 +1,6 @@ export class Rect { + static empty: Rect = new Rect(0, 0, 0, 0); + left: number; top: number; right: number; @@ -40,6 +42,10 @@ export class Rect { const right = Math.min(this.right, rect.right); const bottom = Math.min(this.bottom, rect.bottom); - return new Rect(left, top, right, bottom); + if (right >= left && bottom >= top) { + return new Rect(left, top, right, bottom); + } else { + return Rect.empty; + } } } diff --git a/test/rect.test.ts b/test/rect.test.ts index e423942..5e05bcd 100644 --- a/test/rect.test.ts +++ b/test/rect.test.ts @@ -342,5 +342,20 @@ describe('Rect', () => { is(result.bottom, 20); is(result.left, 10); }); + + it('Should return an empty Rect if two Rect\'s don\'t intersect', () => { + // Arrange + const rectA = new Rect(0, 0, 20, 20); + const rectB = new Rect(30, 30, 50, 50); + + // Act + const result = rectA.getIntersectionWith(rectB); + + // Assert + is(result.top, 0); + is(result.right, 0); + is(result.bottom, 0); + is(result.left, 0); + }); }); });