Skip to content

Instantly share code, notes, and snippets.

@cmrfrd
Created April 1, 2024 22:11
Show Gist options
  • Save cmrfrd/5d3ccda3dc26b65cfd46a93d2fc0f77a to your computer and use it in GitHub Desktop.
Save cmrfrd/5d3ccda3dc26b65cfd46a93d2fc0f77a to your computer and use it in GitHub Desktop.
visibile_elements.ts
const clickableElements = await page.evaluate(async () => {
const resultElements: Elem[] = []
const clickables = [
'a',
'button',
'input',
'textarea',
'select',
'details',
'summary'
]
function isVisible(elem: Element) {
const rect = elem.getBoundingClientRect()
const inViewport =
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= window.innerHeight &&
rect.right <= window.innerWidth
if (!inViewport) return false
const style = getComputedStyle(elem)
if (style.display === 'none') return false
if (style.visibility !== 'visible') return false
const elemCenter = {
x: rect.left + rect.width / 2,
y: rect.top + rect.height / 2
}
if (elemCenter.x < 0) return false
if (
elemCenter.x >
(document.documentElement.clientWidth || window.innerWidth)
)
return false
if (elemCenter.y < 0) return false
if (
elemCenter.y >
(document.documentElement.clientHeight ||
window.innerHeight)
)
return false
let pointContainer = document.elementFromPoint(
elemCenter.x,
elemCenter.y
)
if (!pointContainer) return false
let candidatePointContainer: Element | null
while (pointContainer) {
if (pointContainer === elem) return true
candidatePointContainer =
pointContainer.parentNode as Element
if (!candidatePointContainer) break
pointContainer = candidatePointContainer
}
return false
}
const aspectRatio = (w: number, h: number) => {
if (
typeof w != 'number' ||
typeof h != 'number' ||
isNaN(w) ||
isNaN(h)
)
throw new Error('Invalid input')
if (!w || !h) return 0
return Math.max(w, h) / Math.min(w, h)
}
document.querySelectorAll('body *').forEach((el) => {
if (!el) return
const rect = el.getBoundingClientRect()
if (!isVisible(el)) return
const area = rect.width * rect.height
const ar = aspectRatio(rect.width, rect.height)
if (area < 512 || ar > 16) return
const isClickable =
clickables.includes(el.tagName.toLowerCase()) ||
el.getAttribute('href') != null ||
el.getAttribute('onclick') != null ||
window.getComputedStyle(el).cursor == 'pointer'
if (!isClickable) return
resultElements.push({
element: el,
tag: el.tagName,
area,
aspectRatio: ar,
rect,
html: el.outerHTML
})
})
return resultElements.filter(
(x) =>
!resultElements.some(
(y) => y.html.includes(x.html) && !(x.html == y.html)
)
)
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment