Skip to content

Commit

Permalink
Merge pull request #2 from maxisam/feat/spare-image-placeholder-layer
Browse files Browse the repository at this point in the history
Feat/spare image placeholder layer
  • Loading branch information
maxisam committed Sep 3, 2018
2 parents 6f73e06 + d83215d commit 1facf62
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 81 deletions.
50 changes: 25 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ imports: [
// image width / height ratio for image holder
imageRatio: 16 / 9,
// loading image in placeholder. Can be URL or base64
placeHolderImage:
placeholderImageSrc:
// tslint:disable-next-line:max-line-length
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICA8cGF0aCBmaWxsPSIjZGQwMDMxIiBkPSJNMTI1IDMwTDMxLjkgNjMuMmwxNC4yIDEyMy4xTDEyNSAyMzBsNzguOS00My43IDE0LjItMTIzLjF6Ii8+CiAgPHBhdGggZmlsbD0iI2MzMDAyZiIgZD0iTTEyNSAzMHYyMi4yLS4xVjIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMUwxMjUgMzB6Ii8+CiAgPHBhdGggZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiBmaWxsPSIjZmZmIi8+Cjwvc3ZnPgo='
})
Expand All @@ -70,37 +70,31 @@ $scale: 0.5; // the scale of loading image in place holder

```html
<ngx-progressive-image-loader>
<ngx-image-placeholder>
<ngx-image-placeholder> <!-- ngx-image-placeholder is optional -->
<img [src]="'/assets/Avengers1.jpg'" alt="" ngxProgressiveImage>
</ngx-image-placeholder>
<ngx-image-placeholder>
<img src="/assets/Avengers2.jpg" alt="" ngxProgressiveImage>
</ngx-image-placeholder>
<img src="/assets/Avengers2.jpg" alt="" ngxProgressiveImage>
</ngx-progressive-image-loader>
```

- with img + srcset

```html
<ngx-progressive-image-loader>
<ngx-image-placeholder>
<img [src]="'/assets/Avengers6.jpg'" [srcset]="'/assets/Avengers6.jpg 800w,/assets/Avengers7.jpg 1366w'"
<img [src]="'/assets/Avengers6.jpg'" [srcset]="'/assets/Avengers6.jpg 800w,/assets/Avengers7.jpg 1366w'"
size="(max-width: 1000px) 100vw, 100vw" ngxProgressiveImage>
</ngx-image-placeholder>
</ngx-progressive-image-loader>
```

- with picture element

```html
<ngx-progressive-image-loader>
<ngx-image-placeholder>
<picture ngxProgressiveImage>
<source [srcset]="'/assets/Avengers4.jpg'" media="(max-width: 1000px)">
<source [srcset]="'/assets/Avengers5.jpg'" media="(min-width: 1000px)">
<img [src]="'/assets/Avengers4.jpg'" alt="My default image">
</picture>
</ngx-image-placeholder>
<picture ngxProgressiveImage>
<source [srcset]="'/assets/Avengers4.jpg'" media="(max-width: 1000px)">
<source [srcset]="'/assets/Avengers5.jpg'" media="(min-width: 1000px)">
<img [src]="'/assets/Avengers4.jpg'" alt="My default image">
</picture>
</ngx-progressive-image-loader>
```

Expand All @@ -119,35 +113,41 @@ $scale: 0.5; // the scale of loading image in place holder

Either a single number or an array of numbers which indicate at what percentage of the target's visibility the observer's callback should be executed. If you only want to detect when visibility passes the 50% mark, you can use a value of 0.5. If you want the callback run every time visibility passes another 25%, you would specify the array [0, 0.25, 0.5, 0.75, 1]. The default is 0 (meaning as soon as even one pixel is visible, the callback will be run). A value of 1.0 means that the threshold isn't considered passed until every pixel is visible.

- blurFilter

If you just need blur filter for loading image. Set a number to it.

- filter

If you want to set more [filter](https://developer.mozilla.org/en-US/docs/Web/CSS/filter). It uses the input string as css filter.

- placeHolderImageSrc
- placeholderImageSrc

src for loading image. It works just like src, takes base64 or url.

- imageRatio

image width / height

For `ngx-image-placeholder` directive, it takes
For `ngx-image-placeholder` component, it takes

- imageRatio

- placeholderImageSrc

(after 3.0.0, you can set imageRatio and placeholderImageSrc directly on `ngxProgressiveImage` and spare ngx-image-placeholder layer)

For `ngx-progressive-image-loader` component, it takes

- imageRatio

- placeHolderImageSrc
- placeholderImageSrc

- filter

For `ngx-progressive-image-loader` directive, it takes
For `ngxProgressiveImage` directive, (only for image or source elements)

- imageRatio

- placeHolderImageSrc
- placeholderImageSrc

- filter / blurFilter
- noPlaceholder: boolean; default to `false`, set to `true` will skip image placeholder

## Build project

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"core-js": "^2.5.7",
"rxjs": "^6.3.1",
"zone.js": "~0.8.26",
"ngx-progressive-image-loader": "^2.0.2"
"ngx-progressive-image-loader": "^3.0.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.8.0-rc.0",
Expand Down
2 changes: 1 addition & 1 deletion projects/ngx-progressive-image-loader/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ngx-progressive-image-loader",
"version": "2.0.2",
"version": "3.0.0",
"author": {
"name": "Sam Lin",
"email": "maxisam@gmail.com"
Expand Down
7 changes: 2 additions & 5 deletions projects/ngx-progressive-image-loader/src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { InjectionToken } from '@angular/core';

export interface IImageLoaderOptions extends IntersectionObserverInit {
placeHolderImageSrc?: string;
placeholderImageSrc?: string;
imageRatio: number;
blurFilter: number;
// if filter is set, it will override blurFilter
filter: string;
}
export const IMAGE_LOADER_CONFIG_TOKEN = new InjectionToken<IImageLoaderOptions>('Image loader Config');
Expand All @@ -14,6 +12,5 @@ export const DEFAULT_IMAGE_LOADER_OPTIONS = <IImageLoaderOptions>{
rootMargin: '10px',
threshold: 0.1,
imageRatio: 16 / 9,
blurFilter: 0,
placeHolderImageSrc: ''
placeholderImageSrc: ''
};
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,15 @@ export class ImagePlaceholderComponent implements OnInit {
imageRatio: number;
// a loading image showing before the real image is loaded
@Input()
placeHolderImageSrc: string;
placeholderImageSrc: string;

get imageFilter(): SafeStyle {
if (!this._ProgressiveImageLoader.filter) {
return this.sanitizer.bypassSecurityTrustStyle(`blur(${this._ProgressiveImageLoader.blurFilter}px)`);
}
return this.sanitizer.bypassSecurityTrustStyle(`${this._ProgressiveImageLoader.filter}`);
}

get safeLoadingImage(): SafeStyle {
return this.sanitizer.bypassSecurityTrustUrl(
this.placeHolderImageSrc || this._ProgressiveImageLoader.placeHolderImageSrc
this.placeholderImageSrc || this._ProgressiveImageLoader.placeholderImageSrc
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isPlatformBrowser } from '@angular/common';
import { Component, ElementRef, Inject, Input, OnDestroy, OnInit, PLATFORM_ID, Renderer2 } from '@angular/core';
import { WINDOW } from 'ngx-window-token';

Expand All @@ -12,14 +13,12 @@ export class ProgressiveImageLoaderComponent implements OnInit, OnDestroy {
// define the placeholder height for all images inside this components
@Input()
imageRatio: number;
// to define how blur the loading image is
@Input()
blurFilter: number;

@Input()
filter: string;
// the src of loading image
@Input()
placeHolderImageSrc: string;
placeholderImageSrc: string;
intersectionObserver: IntersectionObserver;

constructor(
Expand All @@ -31,18 +30,16 @@ export class ProgressiveImageLoaderComponent implements OnInit, OnDestroy {
) {}

ngOnInit() {
if (isSupportIntersectionObserver(this.window) && !isSpider(this.window)) {
if (isSupportIntersectionObserver(this.window) && !isSpider(this.window) && isPlatformBrowser(this.platformId)) {
if (!this.imageRatio) {
this.imageRatio = this._ConfigurationService.config.imageRatio;
}
if (!this.blurFilter) {
this.blurFilter = this._ConfigurationService.config.blurFilter;
}

if (!this.filter) {
this.filter = this._ConfigurationService.config.filter;
}
if (!this.placeHolderImageSrc) {
this.placeHolderImageSrc = this._ConfigurationService.config.placeHolderImageSrc;
if (!this.placeholderImageSrc) {
this.placeholderImageSrc = this._ConfigurationService.config.placeholderImageSrc;
}
this.intersectionObserver = new IntersectionObserver(
this.onIntersectionChanged.bind(this),
Expand All @@ -62,6 +59,7 @@ export class ProgressiveImageLoaderComponent implements OnInit, OnDestroy {
observer.unobserve(image);
loadImage(this._Renderer, image);
}

ngOnDestroy(): void {
this.intersectionObserver && this.intersectionObserver.disconnect();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,47 +1,78 @@
import { Directive, ElementRef, Inject, Injector, Input, OnChanges, OnInit, Renderer2, SimpleChanges } from '@angular/core';
import { Directive, ElementRef, Inject, Input, OnChanges, OnInit, Optional, Renderer2, SimpleChanges } from '@angular/core';
import { WINDOW } from 'ngx-window-token';

import { ConfigurationService } from '../configuration.service';
import { ImagePlaceholderComponent } from '../image-placeholder/image-placeholder.component';
import { ProgressiveImageLoaderComponent } from '../progressive-image-loader/progressive-image-loader.component';
import { isSpider, isSupportIntersectionObserver, loadImage } from '../util';
import { isPictureElement, loadImage } from '../util';

@Directive({
// make sure the element is an image element
selector: 'img[ngxProgressiveImage], source[ngxProgressiveImage]'
})
export class ProgressiveImageDirective implements OnInit, OnChanges {
_imageRatio: number;
// to create a placeholder before finish loading the real image to avoid reflow
@Input()
set imageRatio(value: number) {
this._imageRatio = value;
}
get imageRatio() {
return this._imageRatio || this._ProgressiveImageLoader.imageRatio;
}

// a loading image showing before the real image is loaded
_placeholderImageSrc: string;
@Input()
set placeholderImageSrc(value: string) {
this._placeholderImageSrc = value;
}

get placeholderImageSrc(): string {
return this._placeholderImageSrc || this._ProgressiveImageLoader.placeholderImageSrc;
}

@Input()
src: string;
// tslint:disable-next-line:no-input-rename
@Input()
srcset: string;

@Input()
noPlaceholder = false;
imageElement: HTMLImageElement;
isObserve = false;
ProgressiveImageLoader: ProgressiveImageLoaderComponent;
constructor(
private _ElementRef: ElementRef,
public _Renderer: Renderer2,
@Inject(WINDOW) private window: any,
private _Injector: Injector
public _ConfigurationService: ConfigurationService,
@Optional()
@Inject(ImagePlaceholderComponent)
private _ImagePlaceholder: ImagePlaceholderComponent,
@Inject(ProgressiveImageLoaderComponent) private _ProgressiveImageLoader: ProgressiveImageLoaderComponent
) {}
ngOnInit(): void {
this.imageElement = this._ElementRef.nativeElement;
// only image element need to be observe and have onload event
if (isSupportIntersectionObserver(this.window) && !isSpider(this.window)) {
this.isObserve = true;
this.setDataSrc('data-src', this.src);
this.setDataSrc('data-srcset', this.srcset);
if (this._ProgressiveImageLoader.intersectionObserver) {
// only image element need to be observe and have onload event
if (this.imageElement instanceof HTMLImageElement) {
this.isObserve = true;
this._ProgressiveImageLoader.intersectionObserver.observe(this.imageElement);

this.imageElement.onload = () => {
this.imageElement.classList.add('loaded');
};
if (!this._ImagePlaceholder && !this.noPlaceholder) {
this.setPlaceholder();
}
}
} else {
// show image directly
loadImage(this._Renderer, this.imageElement);
}
this.setDataSrc('data-src', this.src);
this.setDataSrc('data-srcset', this.srcset);

this.ProgressiveImageLoader = this._Injector.get(ProgressiveImageLoaderComponent);
this.isObserve && this.ProgressiveImageLoader.intersectionObserver.observe(this.imageElement);
}
ngOnChanges(changes: SimpleChanges): void {
changes.src && !changes.src.isFirstChange() && this.setDataSrc('data-src', this.src);
Expand All @@ -51,11 +82,43 @@ export class ProgressiveImageDirective implements OnInit, OnChanges {
this.isObserve &&
((changes.src && !changes.src.isFirstChange()) || (changes.srcset && !changes.srcset.isFirstChange()))
) {
this.ProgressiveImageLoader.intersectionObserver.unobserve(this.imageElement);
this.ProgressiveImageLoader.intersectionObserver.observe(this.imageElement);
this._ProgressiveImageLoader.intersectionObserver.unobserve(this.imageElement);
this._ProgressiveImageLoader.intersectionObserver.observe(this.imageElement);
}
}
setDataSrc(attr: string, value: string) {
value && this._Renderer.setAttribute(this.imageElement, attr, value);
}

setPlaceholder() {
const parentElement = this.imageElement.parentElement;
const placeholder = this.createPlaceholder(this.createPlaceholderImage());
if (isPictureElement(parentElement)) {
const pictureParent = parentElement.parentElement;
this.insertPlaceholder(pictureParent, parentElement, placeholder);
} else {
this.insertPlaceholder(parentElement, this.imageElement, placeholder);
}
}

insertPlaceholder(parentElement: HTMLElement, imagePicture: HTMLElement, placeholder: HTMLElement) {
parentElement.insertBefore(placeholder, imagePicture);
placeholder.style.paddingBottom = `${100 / this.imageRatio}%`;
placeholder.appendChild(imagePicture);
}

createPlaceholder(placeholderImage: HTMLImageElement) {
const placeholder = document.createElement('div');
placeholder.classList.add('ngx-image-placeholder');
placeholder.appendChild(placeholderImage);
return placeholder;
}

createPlaceholderImage() {
const img = new Image();
img.classList.add('placeholder-loading-image');
img.style.filter = this._ProgressiveImageLoader.filter;
img.src = this.placeholderImageSrc;
return img;
}
}
6 changes: 4 additions & 2 deletions projects/ngx-progressive-image-loader/src/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ export function setAttribute(renderer: Renderer2, attribute: string, element: HT
// maybe doesn't matter
// renderer.removeAttribute(element, 'data-' + attribute);
}

export function isPictureElement(element: HTMLElement) {
return element.nodeName === 'PICTURE';
}
export function loadImage(renderer: Renderer2, image: HTMLImageElement) {
if (image.parentElement.nodeName === 'PICTURE') {
if (isPictureElement(image.parentElement)) {
const sourceElms = image.parentElement.children;
for (let index = 0; index < sourceElms.length; index++) {
const element = sourceElms[index];
Expand Down
Loading

0 comments on commit 1facf62

Please sign in to comment.