@@ -3,9 +3,12 @@ import { For, createMemo, createSignal } from 'solid-js'
33import { useHeadChanges } from '../../hooks/use-head-changes'
44import { useStyles } from '../../styles/use-styles'
55
6- const TITLE_MAX_WIDTH_PX = 600
7- const DESCRIPTION_MAX_WIDTH_PX = 960
8- const DESCRIPTION_MOBILE_MAX_LINES = 3
6+ /** Google typically truncates titles at ~60 characters. */
7+ const TITLE_MAX_CHARS = 60
8+ /** Meta description is often trimmed at ~158 characters on desktop. */
9+ const DESCRIPTION_MAX_CHARS = 158
10+ /** Approximate characters that fit in 3 lines at mobile width (~340px, ~14px font). */
11+ const DESCRIPTION_MOBILE_MAX_CHARS = 120
912const ELLIPSIS = '...'
1013
1114type SerpData = {
@@ -78,18 +81,10 @@ const SERP_PREVIEWS: Array<SerpPreview> = [
7881 } ,
7982]
8083
81- function truncateToWidth (
82- el : HTMLDivElement ,
83- text : string ,
84- maxPx : number ,
85- ) : string {
86- el . textContent = text
87- if ( el . offsetWidth <= maxPx ) return text
88- for ( let i = text . length - 1 ; i >= 0 ; i -- ) {
89- el . textContent = text . slice ( 0 , i ) + ELLIPSIS
90- if ( el . offsetWidth <= maxPx ) return text . slice ( 0 , i ) + ELLIPSIS
91- }
92- return ELLIPSIS
84+ function truncateToChars ( text : string , maxChars : number ) : string {
85+ if ( text . length <= maxChars ) return text
86+ if ( maxChars <= ELLIPSIS . length ) return ELLIPSIS
87+ return text . slice ( 0 , maxChars - ELLIPSIS . length ) + ELLIPSIS
9388}
9489
9590function getSerpFromHead ( ) : SerpData {
@@ -142,9 +137,6 @@ function SerpSnippetPreview(props: {
142137 isMobile : boolean
143138 label : string
144139 issues : Array < string >
145- setTitleMeasureEl : ( el : HTMLDivElement ) => void
146- setDescMeasureEl : ( el : HTMLDivElement ) => void
147- setDescMeasureMobileEl : ( el : HTMLDivElement ) => void
148140} ) {
149141 const styles = useStyles ( )
150142
@@ -177,37 +169,18 @@ function SerpSnippetPreview(props: {
177169 { props . displayTitle || props . data . title || 'No title' }
178170 </ div >
179171 { ! props . isMobile && (
180- < >
181- < div
182- ref = { props . setTitleMeasureEl }
183- class = { `${ styles ( ) . serpSnippetTitle } ${ styles ( ) . serpMeasureHidden } ` }
184- aria-hidden = "true"
185- />
186- < div class = { styles ( ) . serpSnippetDesc } >
187- { props . displayDescription ||
188- props . data . description ||
189- 'No meta description.' }
190- </ div >
191- < div
192- ref = { props . setDescMeasureEl }
193- class = { `${ styles ( ) . serpSnippetDesc } ${ styles ( ) . serpMeasureHidden } ` }
194- aria-hidden = "true"
195- />
196- </ >
172+ < div class = { styles ( ) . serpSnippetDesc } >
173+ { props . displayDescription ||
174+ props . data . description ||
175+ 'No meta description.' }
176+ </ div >
197177 ) }
198178 { props . isMobile && (
199- < >
200- < div class = { styles ( ) . serpSnippetDescMobile } >
201- { props . displayDescription ||
202- props . data . description ||
203- 'No meta description.' }
204- </ div >
205- < div
206- ref = { props . setDescMeasureMobileEl }
207- class = { `${ styles ( ) . serpSnippetDesc } ${ styles ( ) . serpMeasureHiddenMobile } ` }
208- aria-hidden = "true"
209- />
210- </ >
179+ < div class = { styles ( ) . serpSnippetDescMobile } >
180+ { props . displayDescription ||
181+ props . data . description ||
182+ 'No meta description.' }
183+ </ div >
211184 ) }
212185 </ div >
213186 { props . issues . length > 0 ? (
@@ -226,64 +199,27 @@ function SerpSnippetPreview(props: {
226199
227200export function SerpPreviewSection ( ) {
228201 const [ serp , setSerp ] = createSignal < SerpData > ( getSerpFromHead ( ) )
229- const [ titleMeasureEl , setTitleMeasureEl ] = createSignal <
230- HTMLDivElement | undefined
231- > ( undefined )
232- const [ descMeasureEl , setDescMeasureEl ] = createSignal <
233- HTMLDivElement | undefined
234- > ( undefined )
235- const [ descMeasureMobileEl , setDescMeasureMobileEl ] = createSignal <
236- HTMLDivElement | undefined
237- > ( undefined )
238202
239203 useHeadChanges ( ( ) => {
240204 setSerp ( getSerpFromHead ( ) )
241205 } )
242206
243207 const serpPreviewState = createMemo ( ( ) => {
244- const titleEl = titleMeasureEl ( )
245- const descEl = descMeasureEl ( )
246- const descMobileEl = descMeasureMobileEl ( )
247208 const data = serp ( )
248209 const titleText = data . title || 'No title'
249210 const descText = data . description || 'No meta description.'
250211
251- if ( ! titleEl || ! descEl ) {
252- return {
253- displayTitle : titleText ,
254- displayDescription : descText ,
255- overflow : {
256- titleOverflow : false ,
257- descriptionOverflow : false ,
258- descriptionOverflowMobile : false ,
259- } ,
260- }
261- }
262-
263- const displayTitle = truncateToWidth ( titleEl , titleText , TITLE_MAX_WIDTH_PX )
264- const displayDescription = truncateToWidth (
265- descEl ,
266- descText ,
267- DESCRIPTION_MAX_WIDTH_PX ,
268- )
269-
270- let descriptionOverflowMobile = false
271-
272- if ( descMobileEl && descText ) {
273- descMobileEl . textContent = descText
274- const lineHeight =
275- parseFloat ( getComputedStyle ( descMobileEl ) . lineHeight ) || 20
276- const lines = Math . ceil ( descMobileEl . scrollHeight / lineHeight )
277- descriptionOverflowMobile = lines > DESCRIPTION_MOBILE_MAX_LINES
278- }
212+ const displayTitle = truncateToChars ( titleText , TITLE_MAX_CHARS )
213+ const displayDescription = truncateToChars ( descText , DESCRIPTION_MAX_CHARS )
279214
280215 return {
281216 displayTitle,
282217 displayDescription,
283218 overflow : {
284- titleOverflow : displayTitle !== titleText ,
285- descriptionOverflow : displayDescription !== descText ,
286- descriptionOverflowMobile,
219+ titleOverflow : titleText . length > TITLE_MAX_CHARS ,
220+ descriptionOverflow : descText . length > DESCRIPTION_MAX_CHARS ,
221+ descriptionOverflowMobile :
222+ descText . length > DESCRIPTION_MOBILE_MAX_CHARS ,
287223 } ,
288224 }
289225 } )
@@ -311,9 +247,6 @@ export function SerpPreviewSection() {
311247 isMobile = { preview . isMobile }
312248 label = { preview . label }
313249 issues = { issues ( ) }
314- setTitleMeasureEl = { setTitleMeasureEl }
315- setDescMeasureEl = { setDescMeasureEl }
316- setDescMeasureMobileEl = { setDescMeasureMobileEl }
317250 />
318251 )
319252 } }
0 commit comments