Skip to content

Commit

Permalink
New naturalSrcDensity to auto-select image according to screen density
Browse files Browse the repository at this point in the history
This requires a pre-defined URL format and a server that can serve images
at different sizes dynamically
  • Loading branch information
PowerKiKi committed Dec 24, 2020
1 parent 0f2e6ab commit 6ce9cd9
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 0 deletions.
2 changes: 2 additions & 0 deletions projects/natural/src/lib/modules/common/common-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {NaturalDefaultPipe} from './pipes/default.pipe';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatInputModule} from '@angular/material/input';
import {MatSelectModule} from '@angular/material/select';
import {NaturalSrcDensityDirective} from './directives/src-density.directive';

const declarationsToExport = [
NaturalCapitalizePipe,
Expand All @@ -21,6 +22,7 @@ const declarationsToExport = [
NaturalSwissDatePipe,
ReactiveAsteriskDirective,
NaturalHttpPrefixDirective,
NaturalSrcDensityDirective,
NaturalLinkableTabDirective,
];

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {Component, DebugElement} from '@angular/core';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {NaturalSrcDensityDirective} from './src-density.directive';
import {By} from '@angular/platform-browser';

@Component({
template: `
<img naturalSrcDensity />
<img naturalSrcDensity="https://example.com/image/123/200" />
<img [naturalSrcDensity]="'https://example.com/image/123/200'" />
<img [naturalSrcDensity]="'https://example.com/image/123.jpg'" />
<img src="foo.jpg" srcset="bar.jpg" naturalSrcDensity="https://example.com/image/123/200" />
<img src="foo.jpg" srcset="bar.jpg" naturalSrcDensity="https://example.com/image/123.jpg" />
`,
})
class TestComponent {}

describe('NaturalSrcDensityDirective', () => {
let fixture: ComponentFixture<TestComponent>;
let elements: DebugElement[]; // the three elements w/ the directive

const expectedSrc = 'https://example.com/image/123/200';
const expectedSrcset =
'https://example.com/image/123/200, https://example.com/image/123/300 1.5x, https://example.com/image/123/400 2x, https://example.com/image/123/600 3x, https://example.com/image/123/800 4x';
beforeEach(() => {
fixture = TestBed.configureTestingModule({
declarations: [NaturalSrcDensityDirective, TestComponent],
}).createComponent(TestComponent);

fixture.detectChanges(); // initial binding

// all elements with an attached NaturalSrcDensityDirective
elements = fixture.debugElement.queryAll(By.directive(NaturalSrcDensityDirective));
});

it('should have 6 active elements', () => {
expect(elements.length).toBe(6);
});

it('1st should be kept empty and should not be used in real world', () => {
expect(elements[0].nativeElement.src).toBe('');
expect(elements[0].nativeElement.srcset).toBe('');
});

it('2nd should work', () => {
expect(elements[1].nativeElement.src).toBe(expectedSrc);
expect(elements[1].nativeElement.srcset).toBe(expectedSrcset);
});

it('3rd should work', () => {
expect(elements[2].nativeElement.src).toBe(expectedSrc);
expect(elements[2].nativeElement.srcset).toBe(expectedSrcset);
});

it('4th should keep naturalSrcDensity as-is without srcset', () => {
expect(elements[3].nativeElement.src).toBe('https://example.com/image/123.jpg');
expect(elements[3].nativeElement.srcset).toBe('');
});

it('5th will completely discard original src and srcset attributes', () => {
expect(elements[4].nativeElement.src).toBe(expectedSrc);
expect(elements[4].nativeElement.srcset).toBe(expectedSrcset);
});

it('6th will completely discard original src and srcset attributes while keeping naturalSrcDensity as-is', () => {
expect(elements[5].nativeElement.src).toBe('https://example.com/image/123.jpg');
expect(elements[5].nativeElement.srcset).toBe('');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {Directive, ElementRef, Input} from '@angular/core';

@Directive({
selector: 'img[naturalSrcDensity]',
})
export class NaturalSrcDensityDirective {
/**
* Automatically apply image selection based on screen density.
*
* The given URL **MUST** be the normal density URL. And it **MUST** include
* the size as last path segment. That size will automatically be changed
* for other screen densities. That means that the server **MUST** be able to
* serve an image of the given size.
*
* Usage:
*
* ```html
* <img [appSrcset]="'/image/123/200'" />
* ```
*
* Will generate something like:
*
* ```html
* <img src="/image/123/200" srcset="/image/123/200, /image/123/300 1.5x, /image/123/400 2x, /image/123/600 3x" />
* ```
*
* @see https://web.dev/codelab-density-descriptors/
*/
@Input()
public set naturalSrcDensity(src: string) {
if (!src) {
return;
}

const match = src.match(/^(.*\/)(\d+)$/);
const base = match?.[1];
const size = +(match?.[2] ?? 0);

let srcset = '';
if (base && size) {
// This should cover most common densities according to https://www.mydevice.io/#tab1
const size15 = size * 1.5;
const size2 = size * 2;
const size3 = size * 3;
const size4 = size * 4;

srcset = `${base}${size}, ${base}${size15} 1.5x, ${base}${size2} 2x, ${base}${size3} 3x, ${base}${size4} 4x`;
}

this.elementRef.nativeElement.src = src;
this.elementRef.nativeElement.srcset = srcset;
}

constructor(private elementRef: ElementRef<HTMLImageElement>) {}
}
1 change: 1 addition & 0 deletions projects/natural/src/lib/modules/common/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * from './pipes/ellipsis.pipe';
export * from './pipes/enum.pipe';
export * from './pipes/swiss-date.pipe';
export * from './services/memory-storage';
export {NaturalSrcDensityDirective} from './directives/src-density.directive';
export {
NATURAL_SEO_CONFIG,
NaturalSeoConfig,
Expand Down

0 comments on commit 6ce9cd9

Please sign in to comment.