@tvanc/lineclamp
Version:
Limit an element's text to a given height or number of lines.
2 lines (1 loc) • 3.01 kB
JavaScript
class t{constructor(t,{maxLines:e,maxHeight:i,useSoftClamp:s=!1,hardClampAsFallback:n=!0,minFontSize:h=1,maxFontSize:a,ellipsis:l="…"}={}){Object.defineProperty(this,"originalWords",{writable:!1,value:t.textContent.match(/\S+\s*/g)||[]}),Object.defineProperty(this,"updateHandler",{writable:!1,value:()=>this.apply()}),Object.defineProperty(this,"observer",{writable:!1,value:new MutationObserver(this.updateHandler)}),void 0===a&&(a=parseInt(window.getComputedStyle(t).fontSize,10)),this.element=t,this.maxLines=e,this.maxHeight=i,this.useSoftClamp=s,this.hardClampAsFallback=n,this.minFontSize=h,this.maxFontSize=a,this.ellipsis=l}calculateTextMetrics(){const t=this.element,e=t.cloneNode(!0);e.style.cssText+=";min-height:0!important;max-height:none!important",t.replaceWith(e);const i=e.offsetHeight;e.textContent="";const s=e.offsetHeight,n=i-s;e.textContent=" ";const h=e.offsetHeight,a=h-s;e.appendChild(document.createElement("br")),e.appendChild(document.createTextNode(" "));const l=e.offsetHeight-h,o=1+(i-h)/l;return e.replaceWith(t),{textHeight:n,naturalHeightWithOneLine:h,firstLineHeight:a,additionalLineHeight:l,lineCount:o}}watch(){return this._watching||(window.addEventListener("resize",this.updateHandler),this.observer.observe(this.element,{characterData:!0,subtree:!0,childList:!0,attributes:!0}),this._watching=!0),this}unwatch(){return this.observer.disconnect(),window.removeEventListener("resize",this.updateHandler),this._watching=!1,this}apply(){if(this.element.offsetHeight){const t=this._watching;this.unwatch(),this.element.textContent=this.originalWords.join(""),this.useSoftClamp?this.softClamp():this.hardClamp(),t&&this.watch(!1)}return this}hardClamp(t=!0){if(t||this.shouldClamp()){let t;e(1,this.originalWords.length,(e=>(t=this.originalWords.slice(0,e).join(" "),this.element.textContent=t,this.shouldClamp())),((e,s,n)=>{e>s&&(t=this.originalWords.slice(0,n).join(" "));do{t=t.slice(0,-1),this.element.textContent=t+this.ellipsis}while(this.shouldClamp());i(this,"lineclamp.hardclamp"),i(this,"lineclamp.clamp")}))}return this}softClamp(){const t=this.element.style,s=window.getComputedStyle(this.element).fontSize;t.fontSize="";let n,h=!1;e(this.minFontSize,this.maxFontSize,(e=>(t.fontSize=e+"px",n=this.shouldClamp(),n)),((e,i)=>{e>i&&(t.fontSize=i+"px",n=this.shouldClamp()),h=!n}));const a=t.fontSize!==s;return a&&i(this,"lineclamp.softclamp"),!h&&this.hardClampAsFallback?this.hardClamp(!1):a&&i(this,"lineclamp.clamp"),this}shouldClamp(){const{lineCount:t,textHeight:e}=this.calculateTextMetrics();if(void 0!==this.maxHeight&&void 0!==this.maxLines)return e>this.maxHeight||t>this.maxLines;if(void 0!==this.maxHeight)return e>this.maxHeight;if(void 0!==this.maxLines)return t>this.maxLines;throw new Error("maxLines or maxHeight must be set before calling shouldClamp().")}}function e(t,e,i,s){let n=e;for(;e>t;){if(i(n)?e=n:t=n,e-t==1){s(n,t,e);break}n=Math.round((t+e)/2)}}function i(t,e){t.element.dispatchEvent(new CustomEvent(e))}export{t as default};