-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New naturalSrcDensity to auto-select image according to screen density
This requires a pre-defined URL format and a server that can serve images at different sizes dynamically
- Loading branch information
Showing
4 changed files
with
127 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 69 additions & 0 deletions
69
projects/natural/src/lib/modules/common/directives/src-density.directive.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(''); | ||
}); | ||
}); |
55 changes: 55 additions & 0 deletions
55
projects/natural/src/lib/modules/common/directives/src-density.directive.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>) {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters