@7kasper/ngx-fittext
Version:
**@7kasper/ngx-fittext** is an Angular library that allow you fit text in a box or a line.
164 lines (155 loc) • 5.09 kB
text/typescript
import {
Component,
AfterViewInit,
OnDestroy,
Input,
ElementRef,
Renderer2,
Output,
EventEmitter,
ChangeDetectionStrategy,
} from '@angular/core';
import { fromEvent, Observable, Subscription } from 'rxjs';
import { FitLineInfo } from '../../models';
export class FitLineComponent implements AfterViewInit, OnDestroy {
elChildren: [];
stepSize: number = 2;
calcOnResize: boolean = false;
minFontSize: number = 10;
maxFontSize: number = 500;
showInfo = new EventEmitter<FitLineInfo>();
resizeObservable$: Observable<Event>;
resizeSubscription$: Subscription;
constructor(private el: ElementRef, private renderer: Renderer2) {}
ngAfterViewInit(): void {
//resize event
if (this.calcOnResize) {
this.resizeObservable$ = fromEvent(window, 'resize');
this.resizeSubscription$ = this.resizeObservable$.subscribe((evt) => {
this.fitToLine();
});
}
// sometimes cant calculate after view init, i set timeout to fix it
setTimeout(() => {
this.fitToLine();
}, 20);
}
ngOnDestroy() {
// unsubscribe resize event
this.resizeSubscription$.unsubscribe();
}
// fitting line calculation
async fitToLine(delay?: number) {
this.renderer.setStyle(this.el.nativeElement, 'opacity', 0);
// refreshing need delay
if (delay) {
await this.delayView(delay);
}
// get child elements
this.elChildren = this.el.nativeElement.childNodes;
// resize child elements
for (let index = 0; index < this.elChildren.length; index++) {
const element = this.el.nativeElement.childNodes[index];
this.renderer.setStyle(element, 'white-space', `nowrap`);
const fs = getComputedStyle(element).getPropertyValue('font-size');
const { size, unit } = this.splitFontSize(fs);
// if overflow descrease font size
if (this.isOverflow(element)) {
this.resizeToSmall(element, size, unit);
} else {
// increase font size
this.resizeToBig(element, size, unit);
}
}
}
private resizeToSmall(htmlEl: any, fontSize: number, unit: string) {
while (this.isOverflow(htmlEl)) {
fontSize -= this.stepSize;
if (fontSize <= this.minFontSize) {
this.renderer.setStyle(
htmlEl,
'font-size',
`${this.minFontSize}${unit}`
);
this.renderer.setStyle(htmlEl, 'white-space', `normal`);
this.showInfo.emit({
fontSize: this.minFontSize + unit,
effectedHtm: htmlEl,
});
break;
}
this.renderer.setStyle(htmlEl, 'font-size', `${fontSize}${unit}`);
this.showInfo.emit({
fontSize: this.minFontSize + unit,
effectedHtm: htmlEl,
});
}
this.renderer.setStyle(this.el.nativeElement, 'opacity', 1);
}
private resizeToBig(htmlEl: any, fontSize: number, unit: string) {
while (!this.isOverflow(htmlEl)) {
fontSize += this.stepSize;
this.renderer.setStyle(htmlEl, 'font-size', `${fontSize}${unit}`);
if (this.isOverflow(htmlEl)) {
fontSize -= this.stepSize;
this.renderer.setStyle(htmlEl, 'font-size', `${fontSize}${unit}`);
this.showInfo.emit({
fontSize: this.minFontSize + unit,
effectedHtm: htmlEl,
});
break;
}
if (fontSize >= this.maxFontSize) {
this.renderer.setStyle(
htmlEl,
'font-size',
`${this.maxFontSize}${unit}`
);
this.showInfo.emit({
fontSize: this.minFontSize + unit,
effectedHtm: htmlEl,
});
break;
}
this.showInfo.emit({
fontSize: this.minFontSize + unit,
effectedHtm: htmlEl,
});
}
// if calculated font size smaller than min font size
if (fontSize <= this.minFontSize) {
this.renderer.setStyle(htmlEl, 'font-size', `${this.minFontSize}${unit}`);
this.renderer.setStyle(htmlEl, 'white-space', `normal`);
this.showInfo.emit({
fontSize: this.minFontSize + unit,
effectedHtm: htmlEl,
});
}
this.renderer.setStyle(this.el.nativeElement, 'opacity', 1);
}
// refresh if text changed programmatically
public refreshText() {
this.fitToLine(10);
}
private isOverflow(htmlEl: any) {
return htmlEl.scrollWidth > htmlEl.offsetWidth;
}
private splitFontSize(fs: string) {
const size = fs.match(/\d+/g);
const unit = fs.match(/[a-zA-Z]+/g);
return {
size: size ? Number(size[0]) : 2,
unit: unit ? unit[0] : 'px',
};
}
private async delayView(delayMs: number) {
return new Promise((resolve) => setTimeout(resolve, delayMs));
}
}