Skip to content

Instantly share code, notes, and snippets.

@tomhodgins
Last active October 8, 2019 17:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomhodgins/fd699734d6f6cc901b0fd6d46d15ec80 to your computer and use it in GitHub Desktop.
Save tomhodgins/fd699734d6f6cc901b0fd6d46d15ec80 to your computer and use it in GitHub Desktop.
// Point & Click Selector Builder
// Simple CSS Selector
function simpleSelector(node) {
const buildTag = tag => {
let tagName = tag.tagName.toLowerCase()
let className = Array.from(tag.classList).filter(name => name.includes('=') === false).join('.')
let attributes = (
tag.id.length
? `#${tag.id}`
: ''
) + (
className.length
? `.${className}`
: ''
)
return tagName + (attributes.length ? attributes : '')
}
let current = node
let selector = buildTag(current)
while (
current.parentElement
&& current.parentElement !== document.documentElement
) {
selector = `${buildTag(current.parentElement)} ${selector}`
current = current.parentElement
}
return selector
}
// Specific CSS Selector
function superSpecificSelector(node) {
const buildTag = tag => {
const tagName = tag.tagName.toLowerCase()
const attributes = Array.from(tag.attributes)
.reduce(
(acc, attr) => {
switch (attr.name) {
case 'id':
return acc += `#${attr.value}`
case 'class':
let string = attr.value
.split(' ')
.filter(name => {
return name.includes('=') === false
})
.join('.')
return acc += string.length ?
`.${string}`
: ''
default:
return acc += `[${attr.name}="${attr.value.replace(/"/g, '\\"')}"]`
}
},
''
)
let siblings = Array.from(tag.parentNode.children)
let index = ''
if (tag !== document.body) {
if (siblings.indexOf(tag) === 0) {
index = ':first-child'
} else if (siblings.indexOf(tag) === siblings.length) {
index = ':last-child'
} else {
index = `:nth-child(${siblings.indexOf(tag) + 1})`
}
}
return tagName + attributes + index
}
let current = node
let selector = buildTag(current)
while (
current.parentElement
&& current.parentElement !== document.documentElement
) {
selector = `${buildTag(current.parentElement)} > ${selector}`
current = current.parentElement
}
return selector
}
// Tree-based Skeleton Selector
function skeletonSelector(
element = document.documentElement,
option = 'long' || 'short'
) {
let selector = []
let current = element
while (current.parentElement) {
let index = [...current.parentElement.children].indexOf(current)
let formula = {
// *:nth-of-type(2)
short: num => `*:nth-child(${num + 1})`,
// * + *
long: num => '*'.repeat(num + 1).split('').join(' + ')
}
selector.push(formula[option](index))
current = current.parentElement
}
return [...selector, ':root'].reverse().join(' > ')
}
// Try to generate the simplest selector that matches the element
function generateSelector(node) {
return [
simpleSelector(node),
superSpecificSelector(node),
skeletonSelector(node, 'short'),
skeletonSelector(node, 'long')
].find(selector =>
node === document.querySelector(selector)
)
}
// Log generated selector to console on click
window.addEventListener('click', event => {
event.preventDefault()
console.log(generateSelector(event.target))
})
// Add On-Page Hover style effect for showing boundaries of elements
const selectors = Array.from(document.querySelectorAll('*')).map(tag => generateSelector(tag))
document.documentElement.appendChild(
document.createElement('style')
).textContent = `
:hover {
box-shadow: inset red 0 0 0 1px !important;
}
${selectors.join(', ')}:hover {
position: relative !important;
cursor: pointer !important;
}
${selectors.map(selector => `
${selector}:hover::before {
content: "${selector}";
font-size: 10pt !important;
position: absolute !important;
top: 0 !important;
left: 0 !important;
padding: .25em !important;
font-family: monospace !important:
font-weight: 400 !important;
color: black !important;
background-color: white !important;
z-index: 9999 !important;
}
`).join('\n')}
`
;'Point and click to log selector to console';
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment