@tvanc/lineclamp
Version:
Limit an element's text to a given height or number of lines.
2 lines (1 loc) • 3.23 kB
JavaScript
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).LineClamp=e()}(this,(function(){"use strict";function t(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 e(t,e){t.element.dispatchEvent(new CustomEvent(e))}return class{constructor(t,{maxLines:e,maxHeight:i,useSoftClamp:s=!1,hardClampAsFallback:n=!0,minFontSize:h=1,maxFontSize:a,ellipsis:o="…"}={}){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=o}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 o=e.offsetHeight-h,l=1+(i-h)/o;return e.replaceWith(t),{textHeight:n,naturalHeightWithOneLine:h,firstLineHeight:a,additionalLineHeight:o,lineCount:l}}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(i=!0){if(i||this.shouldClamp()){let i;t(1,this.originalWords.length,(t=>(i=this.originalWords.slice(0,t).join(" "),this.element.textContent=i,this.shouldClamp())),((t,s,n)=>{t>s&&(i=this.originalWords.slice(0,n).join(" "));do{i=i.slice(0,-1),this.element.textContent=i+this.ellipsis}while(this.shouldClamp());e(this,"lineclamp.hardclamp"),e(this,"lineclamp.clamp")}))}return this}softClamp(){const i=this.element.style,s=window.getComputedStyle(this.element).fontSize;i.fontSize="";let n,h=!1;t(this.minFontSize,this.maxFontSize,(t=>(i.fontSize=t+"px",n=this.shouldClamp(),n)),((t,e)=>{t>e&&(i.fontSize=e+"px",n=this.shouldClamp()),h=!n}));const a=i.fontSize!==s;return a&&e(this,"lineclamp.softclamp"),!h&&this.hardClampAsFallback?this.hardClamp(!1):a&&e(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().")}}}));