Skip to content

Commit aec7488

Browse files
authored
Fixed placeholder when user passes css style (#284)
* updated placedholder attributes
1 parent cda7f9c commit aec7488

File tree

3 files changed

+51
-73
lines changed

3 files changed

+51
-73
lines changed

projects/angular-cld/src/lib/cloudinary-image.component.spec.ts

Lines changed: 20 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -649,18 +649,12 @@ describe('CloudinaryImage', () => {
649649
expect(img.getAttribute('style')).toEqual(jasmine.stringMatching('opacity: 0; position: absolute;'));
650650
});
651651
});
652-
653-
describe('lazy load image', async () => {
652+
describe('cl-image with placeholder and html style', () => {
654653
@Component({
655-
template: `
656-
<div class="startWindow"><cl-image loading="lazy" width="300" public-id="bear"></cl-image></div>
657-
<div style="margin-top: 300px"><cl-image loading="lazy" width="300" public-id="bear"></cl-image></div>
658-
<div style="margin-top: 300px"><cl-image loading="lazy" width="300" public-id="bear"></cl-image></div>
659-
<div style="margin-top: 300px"><cl-image loading="lazy" width="300" public-id="bear"></cl-image></div>
660-
<div style="margin-top: 300px"><cl-image loading="lazy" width="300" public-id="bear"></cl-image></div>
661-
<div style="margin-top: 300px"><cl-image loading="lazy" width="300" public-id="bear"></cl-image></div>
662-
<div style="margin-top: 300px"><cl-image loading="lazy" width="300" public-id="bear"></cl-image></div>
663-
<div class="endWindow" style="margin-top: 300px"><cl-image loading="lazy" width="300" public-id="bear"></cl-image></div>`
654+
template: `<div style="margin-top: 4000px"></div>
655+
<cl-image loading="lazy" public-id="sample" width="500" crop="fit" style="max-height: 100%">
656+
<cl-placeholder type="blur"></cl-placeholder>
657+
</cl-image>`
664658
})
665659
class TestComponent {}
666660

@@ -670,7 +664,7 @@ describe('CloudinaryImage', () => {
670664
{ cloud_name: '@@fake_angular2_sdk@@', client_hints: true } as CloudinaryConfiguration);
671665
beforeEach(() => {
672666
fixture = TestBed.configureTestingModule({
673-
declarations: [CloudinaryTransformationDirective, CloudinaryImage, TestComponent, LazyLoadDirective],
667+
declarations: [CloudinaryTransformationDirective, CloudinaryImage, TestComponent, CloudinaryPlaceHolder],
674668
providers: [{ provide: Cloudinary, useValue: testLocalCloudinary }]
675669
}).createComponent(TestComponent);
676670

@@ -679,43 +673,17 @@ describe('CloudinaryImage', () => {
679673
des = fixture.debugElement.queryAll(By.directive(CloudinaryImage));
680674
});
681675

682-
it('should load eagerly', () => {
676+
it('should have style opacity and position when style is passed', () => {
683677
const img = des[0].children[0].nativeElement as HTMLImageElement;
684-
expect(img.hasAttribute('data-src')).toBe(true);
685-
expect(img.attributes.getNamedItem('data-src').value).toEqual(jasmine.stringMatching('image/upload/bear'));
686-
});
687-
it('Should lazy load post scroll', async() => {
688-
const delay = 300;
689-
const wait = (ms) => new Promise(res => setTimeout(res, ms));
690-
const count = async () => document.querySelectorAll('.startWindow').length;
691-
const scrollDown = async () => {
692-
document.querySelector('.endWindow')
693-
.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'end' });
694-
}
695-
696-
let preCount = 0;
697-
let postCount = 0;
698-
do {
699-
preCount = await count();
700-
await scrollDown();
701-
await wait(delay);
702-
postCount = await count();
703-
} while (postCount > preCount);
704-
await wait(delay);
705-
706-
const img = des[3].children[0].nativeElement as HTMLImageElement;
707-
expect(img.hasAttribute('src')).toBe(true);
708-
expect(img.attributes.getNamedItem('src').value).toEqual(jasmine.stringMatching('image/upload/bear'));
678+
expect(img.getAttribute('style')).toEqual(jasmine.stringMatching('max-height: 100%; opacity: 0; position: absolute;'));
709679
});
710680
});
711-
describe('lazy load image with default placeholder', async () => {
681+
describe('lazy load image', async () => {
712682
@Component({
713683
template: `
714684
<div class="startWindow"><cl-image loading="lazy" width="300" public-id="bear"></cl-image></div>
715685
<div style="margin-top: 300px"><cl-image loading="lazy" width="300" public-id="bear"></cl-image></div>
716-
<div style="margin-top: 300px"><cl-image loading="lazy" width="300" public-id="bear">
717-
<cl-placeholder></cl-placeholder>
718-
</cl-image></div>
686+
<div style="margin-top: 300px"><cl-image loading="lazy" width="300" public-id="bear"></cl-image></div>
719687
<div style="margin-top: 300px"><cl-image loading="lazy" width="300" public-id="bear"></cl-image></div>
720688
<div style="margin-top: 300px"><cl-image loading="lazy" width="300" public-id="bear"></cl-image></div>
721689
<div style="margin-top: 300px"><cl-image loading="lazy" width="300" public-id="bear"></cl-image></div>
@@ -726,28 +694,25 @@ describe('CloudinaryImage', () => {
726694

727695
let fixture: ComponentFixture<TestComponent>;
728696
let des: DebugElement[]; // the elements w/ the directive
729-
let placeholder: DebugElement[];
730697
let testLocalCloudinary: Cloudinary = new Cloudinary(require('cloudinary-core'),
731698
{ cloud_name: '@@fake_angular2_sdk@@', client_hints: true } as CloudinaryConfiguration);
732-
beforeEach(fakeAsync(() => {
699+
beforeEach(() => {
733700
fixture = TestBed.configureTestingModule({
734-
declarations: [CloudinaryTransformationDirective, CloudinaryImage, TestComponent, LazyLoadDirective, CloudinaryPlaceHolder],
701+
declarations: [CloudinaryTransformationDirective, CloudinaryImage, TestComponent, LazyLoadDirective],
735702
providers: [{ provide: Cloudinary, useValue: testLocalCloudinary }]
736703
}).createComponent(TestComponent);
737704

738705
fixture.detectChanges(); // initial binding
739706
// all elements with an attached CloudinaryImage
740707
des = fixture.debugElement.queryAll(By.directive(CloudinaryImage));
741-
placeholder = fixture.debugElement.queryAll(By.directive(CloudinaryPlaceHolder));
742-
tick();
743-
fixture.detectChanges();
744-
}));
708+
});
709+
745710
it('should load eagerly', () => {
746711
const img = des[0].children[0].nativeElement as HTMLImageElement;
747712
expect(img.hasAttribute('data-src')).toBe(true);
748713
expect(img.attributes.getNamedItem('data-src').value).toEqual(jasmine.stringMatching('image/upload/bear'));
749714
});
750-
it('Should lazy load post scroll', async () => {
715+
it('Should lazy load post scroll', async() => {
751716
const delay = 300;
752717
const wait = (ms) => new Promise(res => setTimeout(res, ms));
753718
const count = async () => document.querySelectorAll('.startWindow').length;
@@ -766,9 +731,7 @@ describe('CloudinaryImage', () => {
766731
} while (postCount > preCount);
767732
await wait(delay);
768733

769-
const placeholderimg = placeholder[0].children[0].nativeElement as HTMLImageElement;
770734
const img = des[3].children[0].nativeElement as HTMLImageElement;
771-
expect(placeholderimg.attributes.getNamedItem('src').value).toEqual(jasmine.stringMatching('image/upload/e_blur:2000,f_auto,q_1/bear'));
772735
expect(img.hasAttribute('src')).toBe(true);
773736
expect(img.attributes.getNamedItem('src').value).toEqual(jasmine.stringMatching('image/upload/bear'));
774737
});
@@ -862,7 +825,7 @@ describe('CloudinaryImage', () => {
862825
tick();
863826
fixture.detectChanges();
864827
const img = des[0].children[0].nativeElement as HTMLImageElement;
865-
expect(img.attributes.getNamedItem('src').value).toEqual(jasmine.stringMatching('c_fit,w_30/e_blur:2000,f_auto,q_1/bear'));
828+
expect(img.attributes.getNamedItem('src').value).toEqual(jasmine.stringMatching('c_fit,w_300/e_blur:2000,f_auto,q_1/bear'));
866829
}));
867830
});
868831
describe('placeholder type pixelate', () => {
@@ -890,7 +853,7 @@ describe('CloudinaryImage', () => {
890853
tick();
891854
fixture.detectChanges();
892855
const img = des[0].children[0].nativeElement as HTMLImageElement;
893-
expect(img.attributes.getNamedItem('src').value).toEqual(jasmine.stringMatching('image/upload/c_fit,w_30/e_pixelate,f_auto,q_1/bear'));
856+
expect(img.attributes.getNamedItem('src').value).toEqual(jasmine.stringMatching('image/upload/c_fit,w_300/e_pixelate,f_auto,q_1/bear'));
894857
}));
895858
});
896859
describe('placeholder type predominant-color with exact dimensions', () => {
@@ -918,7 +881,7 @@ describe('CloudinaryImage', () => {
918881
tick();
919882
fixture.detectChanges();
920883
const img = des[0].children[0].nativeElement as HTMLImageElement;
921-
expect(img.attributes.getNamedItem('src').value).toEqual(jasmine.stringMatching('image/upload/c_fit,h_30,w_30/ar_1,b_auto,' +
884+
expect(img.attributes.getNamedItem('src').value).toEqual(jasmine.stringMatching('image/upload/c_fit,h_300,w_300/ar_1,b_auto,' +
922885
'c_pad,w_iw_div_2/c_crop,g_north_east,h_1,w_1/f_auto,q_auto/bear'));
923886
}));
924887
});
@@ -948,7 +911,7 @@ describe('CloudinaryImage', () => {
948911
fixture.detectChanges();
949912
const img = des[0].children[0].nativeElement as HTMLImageElement;
950913
expect(img.attributes.getNamedItem('src').value).toEqual('http://res.cloudinary.com/@@fake_angular2_sdk@@/image/' +
951-
'upload/c_fit,w_30/$currWidth_w,$currHeight_h/ar_1,b_auto,c_pad,w_iw_div_2/c_crop,g_north_east,h_10,w_10/c_fill,h_$currHeight,w_$currWidth/f_auto,q_auto/bear');
914+
'upload/c_fit,w_300/$currWidth_w,$currHeight_h/ar_1,b_auto,c_pad,w_iw_div_2/c_crop,g_north_east,h_10,w_10/c_fill,h_$currHeight,w_$currWidth/f_auto,q_auto/bear');
952915
}));
953916
});
954917
describe('placeholder type vectorize', () => {
@@ -1004,7 +967,7 @@ describe('CloudinaryImage', () => {
1004967
tick();
1005968
fixture.detectChanges();
1006969
const img = des[0].children[0].nativeElement as HTMLImageElement;
1007-
expect(img.attributes.getNamedItem('src').value).toEqual(jasmine.stringMatching('e_sepia/c_fit,w_30/e_blur:2000,f_auto,q_1/bear'));
970+
expect(img.attributes.getNamedItem('src').value).toEqual(jasmine.stringMatching('e_sepia/c_fit,w_300/e_blur:2000,f_auto,q_1/bear'));
1008971
}));
1009972
});
1010973
describe('cl-image with acessibility modes', () => {

projects/angular-cld/src/lib/cloudinary-image.component.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
SimpleChanges,
1414
OnDestroy,
1515
ContentChild,
16+
Renderer2,
1617
} from '@angular/core';
1718
import { Cloudinary } from './cloudinary.service';
1819
import { CloudinaryTransformationDirective } from './cloudinary-transformation.directive';
@@ -22,9 +23,9 @@ import { accessibilityEffect } from './constants';
2223

2324
@Component({
2425
selector: 'cl-image',
25-
template: `<img [ngStyle]="getPlaceHolderStyle()"(load)="hasLoaded()">
26-
<div *ngIf="placeholderComponent"[style.display]="shouldShowPlaceHolder ? 'inline' : 'none'">
27-
<ng-content></ng-content>
26+
template: `<img (load)="hasLoaded()">
27+
<div *ngIf="placeholderComponent && shouldShowPlaceHolder" [style.display]="shouldShowPlaceHolder ? 'inline' : 'none'">
28+
<ng-content></ng-content>
2829
</div>
2930
`,
3031
})
@@ -35,6 +36,7 @@ export class CloudinaryImage
3536
@Input('loading') loading: string;
3637
@Input('width') width?: string;
3738
@Input('height') height?: string;
39+
3840
@Input('accessibility') accessibility?: string;
3941

4042
@ContentChildren(CloudinaryTransformationDirective)
@@ -48,7 +50,7 @@ export class CloudinaryImage
4850
shouldShowPlaceHolder = true;
4951
options: object = {};
5052

51-
constructor(private el: ElementRef, private cloudinary: Cloudinary) {}
53+
constructor(private el: ElementRef, private cloudinary: Cloudinary, private renderer: Renderer2) {}
5254

5355
ngOnInit(): void {
5456
if (isBrowser()) {
@@ -94,9 +96,11 @@ export class CloudinaryImage
9496
}
9597
}
9698

97-
getPlaceHolderStyle() {
98-
return {[this.shouldShowPlaceHolder ? 'opacity' : ''] : '0',
99-
[this.shouldShowPlaceHolder ? 'position' : ''] : 'absolute'}
99+
setPlaceHolderStyle() {
100+
if (this.placeholderComponent) {
101+
this.renderer.setStyle(this.el.nativeElement.children[0], 'opacity', '0' );
102+
this.renderer.setStyle(this.el.nativeElement.children[0], 'position', 'absolute' );
103+
}
100104
}
101105

102106
hasLoaded() {
@@ -138,7 +142,6 @@ export class CloudinaryImage
138142
options.src = this.accessibilityModeHandler();
139143
}
140144
const imageTag = this.cloudinary.imageTag(this.publicId, options);
141-
142145
this.setElementAttributes(image, imageTag.attributes());
143146
if (options.responsive) {
144147
this.cloudinary.responsive(image, options);
@@ -149,8 +152,13 @@ export class CloudinaryImage
149152
setElementAttributes(element, attributesLiteral) {
150153
Object.keys(attributesLiteral).forEach(attrName => {
151154
const attr = attrName === 'src' && this.loading === 'lazy' ? 'data-src' : attrName;
152-
element.setAttribute(attr, attributesLiteral[attrName]);
155+
this.renderer.setAttribute(element, attr, attributesLiteral[attrName]);
153156
});
157+
158+
// Enforcing placeholder style
159+
if (this.shouldShowPlaceHolder) {
160+
this.setPlaceHolderStyle();
161+
}
154162
}
155163

156164
/**
@@ -163,13 +171,8 @@ export class CloudinaryImage
163171
if (placeholderOptions['width']) {
164172
if (placeholderOptions['width'] === 'auto') {
165173
placeholderOptions['width'] = image.getAttribute('data-width');
166-
} else if (this.placeholderComponent.type !== 'vectorize') {
167-
placeholderOptions['width'] = Math.ceil(parseInt(options['width'], 10) * 0.1);
168174
}
169175
}
170-
if (placeholderOptions['height']) {
171-
placeholderOptions['height'] = Math.ceil(parseInt(options['height'], 10) * 0.1);
172-
}
173176
this.placeholderComponent.options = placeholderOptions;
174177
}
175178

projects/angular-cld/src/lib/cloudinary-placeholder.component.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import {
33
Component,
44
HostBinding,
55
Input,
6+
ElementRef,
7+
Renderer2,
68
} from '@angular/core';
79
import {Cloudinary} from './cloudinary.service';
810
import { placeholderImageOptions, predominantColorTransformPxl } from './constants';
911

1012
@Component({
1113
selector: 'cl-placeholder',
12-
template: `<img [src]="this.placeholderImg" [style.width.px]="this.itemWidth" [style.height.px]="this.itemHeight">`
14+
template: `<img [src]="this.placeholderImg">`
1315
,
1416
})
1517
export class CloudinaryPlaceHolder implements AfterContentChecked {
@@ -21,7 +23,7 @@ export class CloudinaryPlaceHolder implements AfterContentChecked {
2123
options: object = {};
2224
placeholderImg: string;
2325

24-
constructor(private cloudinary: Cloudinary) {}
26+
constructor(private cloudinary: Cloudinary, private renderer: Renderer2, private el: ElementRef) {}
2527

2628
setWidth(width) {
2729
this.itemWidth = width;
@@ -36,6 +38,8 @@ export class CloudinaryPlaceHolder implements AfterContentChecked {
3638
}
3739

3840
ngAfterContentChecked() {
41+
const imageTag = this.cloudinary.imageTag(this.publicId, this.options);
42+
this.setElementAttributes(this.el.nativeElement.children[0], imageTag.attributes());
3943
this.placeholderImg = this.getPlaceholderImage();
4044
}
4145

@@ -46,4 +50,12 @@ export class CloudinaryPlaceHolder implements AfterContentChecked {
4650
return this.cloudinary.url(this.publicId, {transformation: [this.options, ...(placeholderImageOptions[this.type] || placeholderImageOptions['blur'])]});
4751
}
4852
}
53+
54+
setElementAttributes(element, attributesLiteral) {
55+
Object.keys(attributesLiteral).forEach(attrName => {
56+
if (attrName !== 'src') {
57+
this.renderer.setAttribute(element, attrName, attributesLiteral[attrName]);
58+
}
59+
});
60+
}
4961
}

0 commit comments

Comments
 (0)