{"id":24502453,"url":"https://github.com/NullVoxPopuli/render-scan","last_synced_at":"2025-10-02T18:31:25.097Z","repository":{"id":266348143,"uuid":"898107569","full_name":"NullVoxPopuli/render-scan","owner":"NullVoxPopuli","description":null,"archived":false,"fork":false,"pushed_at":"2024-12-04T22:11:58.000Z","size":24,"stargazers_count":50,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-14T21:53:02.595Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/NullVoxPopuli.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-12-03T19:56:39.000Z","updated_at":"2024-12-18T15:05:28.000Z","dependencies_parsed_at":"2024-12-03T20:45:33.730Z","dependency_job_id":null,"html_url":"https://github.com/NullVoxPopuli/render-scan","commit_stats":null,"previous_names":["nullvoxpopuli/render-scan"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NullVoxPopuli%2Frender-scan","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NullVoxPopuli%2Frender-scan/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NullVoxPopuli%2Frender-scan/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NullVoxPopuli%2Frender-scan/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/NullVoxPopuli","download_url":"https://codeload.github.com/NullVoxPopuli/render-scan/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235033794,"owners_count":18925498,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-01-21T23:01:56.493Z","updated_at":"2025-10-02T18:31:24.752Z","avatar_url":"https://github.com/NullVoxPopuli.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# render-scan\n\nVisualize performance!\n\nrender-scan will show you whenever something in the DOM updates, and what kind of update ocurred.\n\nThis may be important information for framework authors, so that the rendering parts of their libraries can dedupe what would otherwise be no-op renders. \n\n## Bookmarklet\n\n\u003cdetails\u003e\u003csummary\u003eThe easiest way to check render perf is via this bookmarklet.\u003c/summary\u003e\n\n```js\njavascript:(function()%7Blet%20overlay%20%3D%20document.createElement('div')%3B%0Alet%20px%20%3D%20'px'%3B%0Alet%20body%20%3D%20document.body%3B%0Alet%20styles%20%3D%20%60%0A%20%20.render-perf__container%20%7B%0A%20%20%20%20position%3A%20fixed%3B%0A%20%20%20%20pointer-events%3A%20none%3B%0A%20%20%20%20top%3A%200%3B%0A%20%20%20%20left%3A%200%3B%0A%20%20%20%20right%3A%200%3B%0A%20%20%20%20bottom%3A%200%3B%0A%20%20%20%20box-sizing%3A%20border-box%3B%0A%20%20%20%20z-index%3A%201000000000%3B%0A%20%20%20%20%0A%20%20%20%20.render-perf__box%20%7B%0A%20%20%20%20%20%20box-sizing%3A%20border-box%3B%0A%20%20%20%20%20%20font-family%3A%20sans-serif%3B%0A%20%20%20%20%20%20display%3A%20block%3B%0A%20%20%20%20%20%20position%3A%20fixed%3B%0A%20%20%20%20%20%20pointer-events%3A%20none%3B%0A%20%20%20%20%20%20border%3A%201px%20solid%20%23aa00ff%3B%0A%20%20%20%20%20%20background-color%3A%20rgba(200%2C%200%2C%20255%2C%200.05)%3B%0A%20%20%20%20%20%20transition%3A%20opacity%200.25s%3B%0A%0A%20%20%20%20%20%20.render-perf__title%20%7B%0A%20%20%20%20%20%20%20%20display%3A%20inline-block%3B%0A%20%20%20%20%20%20%20%20min-width%3A%20max-content%3B%0A%20%20%20%20%20%20%20%20box-sizing%3A%20content-box%3B%0A%20%20%20%20%20%20%20%20font-size%3A%200.75rem%3B%0A%20%20%20%20%20%20%20%20border%3A%201px%20solid%20%23aa00ff%3B%0A%20%20%20%20%20%20%20%20border-bottom%3A%20none%3B%0A%20%20%20%20%20%20%20%20padding%3A%200.125rem%200.5rem%3B%0A%20%20%20%20%20%20%20%20color%3A%20white%3B%0A%20%20%20%20%20%20%20%20background-color%3A%20rgba(100%2C%200%2C%20200%2C%201)%3B%0A%20%20%20%20%20%20%20%20position%3A%20relative%3B%0A%20%20%20%20%20%20%20%20top%3A%20-1.5rem%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%60%3B%0Alet%20sheet%20%3D%20document.createElement('style')%3B%0Asheet.innerHTML%20%3D%20styles%3B%0Aoverlay.appendChild(sheet)%3B%0Aoverlay.classList.add('render-perf__container')%0Abody.appendChild(overlay)%3B%0A%0A%0Aclass%20Highlight%20%7B%0A%20%20%23element%3B%0A%20%20%23title%3B%0A%20%20constructor(target)%20%7B%0A%20%20%20%20this.target%20%3D%20target%3B%0A%0A%20%20%20%20this.%23element%20%3D%20document.createElement('div')%3B%0A%20%20%20%20this.%23title%20%3D%20document.createElement('div')%3B%0A%20%20%20%20this.%23element.classList.add('render-perf__box')%3B%0A%20%20%20%20this.%23title.classList.add('render-perf__title')%3B%0A%20%20%20%20this.%23element.appendChild(this.%23title)%3B%0A%20%20%7D%0A%0A%20%20%23reasons%20%3D%20new%20Map()%3B%0A%20%20%23totalRerenders%20%3D%200%3B%0A%0A%20%20notify(why)%20%7B%0A%20%20%20%20this.%23reasons.set(why%2C%20(this.%23reasons.get(why)%20%7C%7C%200)%20%2B%201)%3B%0A%20%20%20%20this.%23totalRerenders%2B%2B%3B%0A%0A%20%20%20%20this.%23render()%3B%0A%20%20%7D%0A%0A%20%20get%20title()%20%7B%0A%20%20%20%20let%20reasons%20%3D%20%5B...this.%23reasons.entries()%5D.map((%5Bkey%2C%20count%5D)%20%3D%3E%20%60%24%7Bkey%7D%20(%24%7Bcount%7D)%60).join('%20%2C%20')%3B%0A%20%20%20%20let%20total%20%3D%200%3B%0A%0A%20%20%20%20this.%23reasons.values().forEach(x%20%3D%3E%20total%20%2B%3D%20x)%3B%0A%0A%20%20%20%20return%20%60x%24%7Btotal%7D%20%7C%20%24%7Breasons%7D%60%3B%0A%20%20%7D%0A%0A%20%20%23fadeOut%3B%0A%20%20%23frame%3B%0A%20%20%23render()%20%7B%0A%20%20%20%20cancelAnimationFrame(this.%23frame)%3B%0A%20%20%20%20clearTimeout(this.%23fadeOut)%3B%0A%20%20%20%20this.%23frame%20%3D%20requestAnimationFrame(()%20%3D%3E%20%7B%0A%20%20%20%20%20%20let%20rect%20%3D%20this.%23getRect()%3B%0A%0A%20%20%20%20%20%20if%20(!rect)%20return%3B%0A%20%20%20%20%20%20if%20(rect.top%20%3E%20window.innerHeight)%20return%3B%0A%20%20%20%20%20%20if%20(rect.left%20%3E%20window.innerWidth)%20return%3B%0A%0A%20%20%20%20%20%20if%20(!overlay.contains(this.%23element))%20%7B%0A%20%20%20%20%20%20%20%20overlay.appendChild(this.%23element)%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20this.%23title.textContent%20%3D%20this.title%3B%0A%0A%20%20%20%20%20%20Object.assign(this.%23element.style%2C%20%7B%0A%20%20%20%20%20%20%20%20top%3A%20rect.y%20%2B%20px%2C%0A%20%20%20%20%20%20%20%20left%3A%20rect.x%20%2B%20px%2C%0A%20%20%20%20%20%20%20%20width%3A%20rect.width%20%2B%20px%2C%0A%20%20%20%20%20%20%20%20height%3A%20rect.height%20%2B%20px%2C%0A%20%20%20%20%20%20%7D)%3B%0A%0A%20%20%20%20%20%20this.%23element.style.opacity%20%3D%201%3B%0A%20%20%20%20%20%20this.%23fadeOut%20%3D%20setTimeout(()%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20this.%23element.style.opacity%20%3D%200%3B%0A%20%20%20%20%20%20%20%20this.%23reasons.clear()%3B%0A%20%20%20%20%20%20%7D%2C%201000)%3B%0A%20%20%20%20%7D)%0A%20%20%7D%0A%0A%20%20%23getRect()%20%7B%0A%20%20%20%20if%20(this.target%20instanceof%20Element)%20%7B%0A%20%20%20%20%20%20let%20rect%20%3D%20this.target.getBoundingClientRect()%3B%0A%0A%20%20%20%20%20%20return%20%7B%0A%20%20%20%20%20%20%20%20x%3A%20rect.x%2C%0A%20%20%20%20%20%20%20%20y%3A%20rect.y%2C%0A%20%20%20%20%20%20%20%20width%3A%20rect.width%2C%0A%20%20%20%20%20%20%20%20height%3A%20rect.height%2C%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%0A%20%20%20%20let%20range%20%3D%20document.createRange()%3B%0A%20%20%20%20range.selectNodeContents(this.target)%3B%0A%20%20%20%20let%20rects%20%3D%20range.getClientRects()%3B%0A%20%20%20%20let%20rect%20%3D%20rects%5B0%5D%3B%0A%0A%20%20%20%20if%20(!rect)%20%7B%0A%20%20%20%20%20%20console.log(%60Could%20not%20determine%20coordinates%20of%20%60%2C%20this.target)%3B%0A%20%20%20%20%20%20return%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20return%20%7B%0A%20%20%20%20%20%20x%3A%20rect.x%2C%0A%20%20%20%20%20%20y%3A%20rect.y%2C%0A%20%20%20%20%20%20width%3A%20rect.width%2C%0A%20%20%20%20%20%20height%3A%20rect.height%2C%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D%0Alet%20cache%20%3D%20new%20WeakMap()%3B%0Afunction%20highlightNode(element%2C%20why)%20%7B%0A%20%20let%20existing%20%3D%20cache.get(element)%3B%0A%0A%20%20if%20(!existing)%20%7B%0A%20%20%20%20existing%20%3D%20new%20Highlight(element)%3B%0A%20%20%20%20cache.set(element%2C%20existing)%3B%0A%20%20%7D%0A%0A%20%20existing.notify(why)%3B%0A%7D%0A%0Alet%20mutationObserver%20%3D%20new%20MutationObserver((mutationList%2C%20observer)%20%3D%3E%20%7B%0A%20%20for%20(let%20mutation%20of%20mutationList)%20%7B%0A%20%20%20%20if%20(typeof%20mutation.target.getAttribute%20%3D%3D%3D%20'function')%20%7B%0A%20%20%20%20%20%20let%20shouldIgnore%20%3D%20mutation.target.getAttribute('class')%3F.includes('render-perf__')%0A%0A%20%20%20%20%20%20if%20(shouldIgnore)%20%7B%0A%20%20%20%20%20%20%20%20continue%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%0A%20%20%20%20switch%20(mutation.type)%20%7B%0A%20%20%20%20%20%20case%20%22attributes%22%3A%0A%20%20%20%20%20%20%20%20highlightNode(mutation.target%2C%20'attribute%20changed')%3B%0A%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20case%20%22childList%22%3A%0A%20%20%20%20%20%20%20%20highlightNode(mutation.target%2C%20'children%20changed')%3B%0A%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20case%20%22characterData%22%3A%0A%20%20%20%20%20%20%20%20highlightNode(mutation.target%2C%20'text%20changed')%3B%0A%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20default%3A%0A%20%20%20%20%20%20%20%20console.log(%60Unhandled%20mutation%20type%3A%20%24%7Bmutation.type%7D%60)%3B%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D)%3B%0A%0AmutationObserver.observe(body%2C%20%7B%0A%20%20subtree%3A%20true%2C%0A%20%20childList%3A%20true%2C%0A%20%20attributes%3A%20true%2C%0A%20%20characterData%3A%20true%2C%0A%7D)%3B%7D)()%3B\n```\n\nCopy the text into the URL field of a new bookmark!\n\nBookmarklet made with [bookmarklet-maker](https://caiorss.github.io/bookmarklet-maker/)\n\n\u003c/details\u003e\n\n## Install\n\n```bash\nnpm add render-scan\n```\n\n## Usage\n\nto use in development:\n\n\n```js\nimport 'render-scan'\n```\n\nto use _only_ in development:\n\n```js\nif (import.meta.env.DEV) {\n    import('render-scan');\n}\n```\n\nto use on any site:\n- copy the `index.js` contents\n- navigate to the site you want to instrument\n- open the dev tools and open the console\n- paste the contents of your clipboard into the console\n- run the pasted code\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FNullVoxPopuli%2Frender-scan","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FNullVoxPopuli%2Frender-scan","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FNullVoxPopuli%2Frender-scan/lists"}