Skip to content

Instantly share code, notes, and snippets.

@nolanlawson
Created April 14, 2021 21:31
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 nolanlawson/370c624f9dc1fcd03916f491d777e402 to your computer and use it in GitHub Desktop.
Save nolanlawson/370c624f9dc1fcd03916f491d777e402 to your computer and use it in GitHub Desktop.
Selector perf test, more realistic scenario for attribute values
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Selector perf test (more realistic scenario for attribute values)</title>
<style>
pre {
position: fixed;
right: 0;
top: 0;
padding: 20px;
background: rgba(30, 30, 30, 0.8);
color: white;
pointer-events: none;
}
.container {
padding: 20px 0;
}
</style>
</head>
<body>
<h1>Selector perf test (more realistic scenario for attribute values)</h1>
<h2>Mode: <span class="mode"></span></h2>
<h2>Number of rules: <span class="rules"></span></h2>
<button onclick="swap()">Force style recalc</button>
<div class="container"></div>
<pre></pre>
<script type="module">
const params = new URLSearchParams(location.search)
const mode = params.get('mode') || 'classes'
const numRules = parseInt(params.get('rules'), 10) || 0
const pre = document.querySelector('pre')
const NUM_DOM_NODES_LEVEL_1 = 10
const NUM_DOM_NODES_LEVEL_2 = 10
const DOM_DEPTH = 10
const COLORS = ['red', 'blue', 'green']
const log = str => {
pre.textContent += str + '\n'
}
const makeBigDomTree = color => {
const tagName = mode === 'tags' ? `c-${color}` : 'div'
const el = document.createElement(tagName)
if (mode === 'attributes') {
el.setAttribute('data-' + color, '')
} else if (mode === 'attributeValues') {
el.setAttribute('data-x-color', color)
} else if (mode === 'classes') {
el.classList.add(color)
}
for (let i = 0; i < NUM_DOM_NODES_LEVEL_1; i++) {
const row = document.createElement('div')
for (let j = 0; j < NUM_DOM_NODES_LEVEL_2; j++) {
let deep = document.createElement('div')
let current = deep
for (let k = 0; k < DOM_DEPTH; k++) {
let inner = document.createElement('div')
current.appendChild(inner)
current = inner
}
current.textContent = 'lalala'
row.appendChild(deep)
}
el.appendChild(row)
}
document.querySelector('.container').appendChild(el)
}
for (const color of COLORS) {
makeBigDomTree(color)
}
const style = document.createElement('style')
const randoStyles = () => {
return Array(numRules).fill().map((_, i) => {
let selector
let str = `foo-${i.toString(16)}`
if (mode === 'attributes') {
selector = `[data-${str}] div`
} else if (mode === 'attributeValues') {
selector = `[data-color="${str}"] div`
} else if (mode === 'tags') {
selector = `c-${str} div`
} else {
selector = `.${str} div`
}
return `${selector} { color: gray; }`
}).join('\n')
}
style.textContent = COLORS.map(color => {
let selector
if (mode === 'attributes') {
selector = `[data-${color}] div`
} else if (mode === 'attributeValues') {
selector = `[data-x-color="${color}"] div`
} else if (mode === 'tags') {
selector = `c-${color} div`
} else {
selector = `.${color} div`
}
return `${selector} {
color: ${color};
}`
}).join('\n') + '\n' + randoStyles()
// similar to https://github.com/WICG/request-post-animation-frame
const afterFrame = callback => {
addEventListener('message', callback, { once: true })
postMessage('', '*')
}
const measureStyleLayout = () => {
performance.mark('start')
afterFrame(() => {
performance.measure('total', 'start')
const { duration } = performance.getEntriesByName('total').slice(-1)[0]
log('duration: ' + duration + ' ms')
})
}
requestAnimationFrame(() => {
document.head.appendChild(style)
measureStyleLayout()
})
const colorElements = [...document.querySelectorAll('.container > *')]
const nextColor = color => {
let idx = COLORS.indexOf(color) + 1
if (idx === COLORS.length) {
idx = 0
}
return COLORS[idx]
}
window.swap = () => {
if (mode === 'tags') {
alert('swapping tag names not supported')
return
}
requestAnimationFrame(() => {
for (const el of colorElements) {
if (mode === 'attributes') {
for (const color of COLORS) {
if (el.hasAttribute(`data-${color}`)) {
el.removeAttribute(`data-${color}`)
el.setAttribute(`data-${nextColor(color)}`, '')
break
}
}
} else if (mode === 'attributeValues') {
el.setAttribute(`data-x-color`, nextColor(el.getAttribute('data-x-color')))
} else if (mode === 'classes') {
for (const color of COLORS) {
if (el.classList.contains(color)) {
el.classList.remove(color)
el.classList.add(nextColor(color))
break
}
}
}
}
measureStyleLayout()
})
}
document.querySelector('.mode').innerText = mode
document.querySelector('.rules').innerText = numRules
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment