-
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
[naturalBackgroundDensity]
for Retina support in background images
Usage: ```html <div naturalBackgroundDensity="/api/image/123/200"></div> <div naturalBackgroundDensity="/non-responsive.jpg"></div> <div naturalBackgroundDensity="url(data:image/png;base64,aabbcc)"></div> ``` Will generate something like: ```html <div style="background-image: image-set(url("/api/image/123/200") 1x, url("/api/image/123/300") 1.5x, ...);"></div> <div style="background-image: url("/non-responsive.jpg");"></div> <div style="background-image: url(data:image/png;base64,aabbcc)"></div> ```
- Loading branch information
Showing
7 changed files
with
182 additions
and
28 deletions.
There are no files selected for viewing
82 changes: 82 additions & 0 deletions
82
projects/natural/src/lib/modules/common/directives/background-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,82 @@ | ||
import {Component, DebugElement} from '@angular/core'; | ||
import {ComponentFixture, TestBed} from '@angular/core/testing'; | ||
import {By} from '@angular/platform-browser'; | ||
import {NaturalBackgroundDensityDirective} from './background-density.directive'; | ||
|
||
@Component({ | ||
template: ` | ||
<div naturalBackgroundDensity></div> | ||
<div naturalBackgroundDensity="https://example.com/api/image/123/200" i18n-naturalBackgroundDensity></div> | ||
<div [naturalBackgroundDensity]="'https://example.com/api/image/123/200'"></div> | ||
<div [naturalBackgroundDensity]="'https://example.com/api/image/123.jpg'"></div> | ||
<div | ||
style="color: red; background-image: url('foo.jpg');" | ||
naturalBackgroundDensity="https://example.com/api/image/123/200" i18n-naturalBackgroundDensity | ||
></div> | ||
<div | ||
style="color: red; background-image: url('foo.jpg');" | ||
naturalBackgroundDensity="https://example.com/api/image/123.jpg" i18n-naturalBackgroundDensity | ||
></div> | ||
<div naturalBackgroundDensity="https://example.com/api/image/123/201" i18n-naturalBackgroundDensity></div> | ||
<div naturalBackgroundDensity="url(data:image/png;base64,aabbcc)" i18n-naturalBackgroundDensity></div> | ||
`, | ||
standalone: true, | ||
imports: [NaturalBackgroundDensityDirective], | ||
}) | ||
class TestComponent {} | ||
|
||
describe('NaturalBackgroundDensity', () => { | ||
let fixture: ComponentFixture<TestComponent>; | ||
let elements: DebugElement[]; // the elements with the directive | ||
|
||
const expected = | ||
'image-set(url("https://example.com/api/image/123/200") 1x, url("https://example.com/api/image/123/300") 1.5x, url("https://example.com/api/image/123/400") 2x, url("https://example.com/api/image/123/600") 3x, url("https://example.com/api/image/123/800") 4x)'; | ||
|
||
beforeEach(() => { | ||
fixture = TestBed.createComponent(TestComponent); | ||
|
||
fixture.detectChanges(); // initial binding | ||
|
||
// all elements with an attached NaturalBackgroundDensity | ||
elements = fixture.debugElement.queryAll(By.directive(NaturalBackgroundDensityDirective)); | ||
}); | ||
|
||
it('should have 8 active elements', () => { | ||
expect(elements.length).toBe(8); | ||
}); | ||
|
||
it('1st should be kept empty and should not be used in real world', () => { | ||
expect(elements[0].nativeElement.style.backgroundImage).toBe(''); | ||
}); | ||
|
||
it('2nd should work', () => { | ||
expect(elements[1].nativeElement.style.backgroundImage).toBe(expected); | ||
}); | ||
|
||
it('3rd should work', () => { | ||
expect(elements[2].nativeElement.style.backgroundImage).toBe(expected); | ||
}); | ||
|
||
it('4th should support a non-responsive URL', () => { | ||
expect(elements[3].nativeElement.style.backgroundImage).toBe('url("https://example.com/api/image/123.jpg")'); | ||
}); | ||
|
||
it('5th will completely discard original src and srcset attributes', () => { | ||
expect(elements[4].nativeElement.style.backgroundImage).toBe(expected); | ||
}); | ||
|
||
it('6th will completely discard original src and srcset attributes while keeping naturalBackgroundDensity as-is', () => { | ||
expect(elements[5].nativeElement.style.backgroundImage).toBe('url("https://example.com/api/image/123.jpg")'); | ||
}); | ||
|
||
it('7th will round dimensions', () => { | ||
const expectedSrcsetUneven = | ||
'image-set(url("https://example.com/api/image/123/201") 1x, url("https://example.com/api/image/123/302") 1.5x, url("https://example.com/api/image/123/402") 2x, url("https://example.com/api/image/123/603") 3x, url("https://example.com/api/image/123/804") 4x)'; | ||
|
||
expect(elements[6].nativeElement.style.backgroundImage).toBe(expectedSrcsetUneven); | ||
}); | ||
|
||
it('8th will allow data url', () => { | ||
expect(elements[7].nativeElement.style.backgroundImage).toBe('url("data:image/png;base64,aabbcc")'); | ||
}); | ||
}); |
58 changes: 58 additions & 0 deletions
58
projects/natural/src/lib/modules/common/directives/background-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,58 @@ | ||
import {Directive, ElementRef, Input} from '@angular/core'; | ||
import {densities} from './src-density.directive'; | ||
|
||
@Directive({ | ||
selector: '[naturalBackgroundDensity]', | ||
standalone: true, | ||
}) | ||
export class NaturalBackgroundDensityDirective { | ||
/** | ||
* Automatically apply background 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. | ||
* | ||
* If the given URL starts with `url(`, or is not ending with a number, then | ||
* it will be set as-is, without any processing. This allows using url data, | ||
* such as `url(data:image/png;base64,aabbcc)`. | ||
* | ||
* Usage: | ||
* | ||
* ```html | ||
* <div naturalBackgroundDensity="/api/image/123/200"></div> | ||
* <div naturalBackgroundDensity="/non-responsive.jpg"></div> | ||
* <div naturalBackgroundDensity="url(data:image/png;base64,aabbcc)"></div> | ||
* ``` | ||
* | ||
* Will generate something like: | ||
* | ||
* ```html | ||
* <div style="background-image: image-set(url("/api/image/123/200") 1x, url("/api/image/123/300") 1.5x, url("/api/image/123/400") 2x, url("/api/image/123/600") 3x, url("/api/image/123/800") 4x);"></div> | ||
* <div style="background-image: url("/non-responsive.jpg");"></div> | ||
* <div style="background-image: url(data:image/png;base64,aabbcc)"></div> | ||
* ``` | ||
* | ||
* See https://developer.mozilla.org/en-US/docs/Web/CSS/image/image-set | ||
*/ | ||
@Input({required: true}) | ||
public set naturalBackgroundDensity(src: string) { | ||
if (src.startsWith('url(')) { | ||
this.elementRef.nativeElement.style.backgroundImage = src; | ||
return; | ||
} | ||
|
||
// Always include a fallback with standard syntax for browsers that don't support at all, or don't support without | ||
// prefixes (eg: Chrome v88 that we still see in production) | ||
const fallback = src ? `url(${src})` : ''; | ||
this.elementRef.nativeElement.style.backgroundImage = fallback; | ||
|
||
const responsive = densities(src, true); | ||
if (responsive) { | ||
this.elementRef.nativeElement.style.backgroundImage = responsive; | ||
} | ||
} | ||
|
||
public constructor(private readonly elementRef: ElementRef<HTMLElement>) {} | ||
} |
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
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
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
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
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