Skip to content

Latest commit

Β 

History

History
406 lines (334 loc) Β· 15.9 KB

File metadata and controls

406 lines (334 loc) Β· 15.9 KB

DOM Analyzer Console Script

A powerful browser console script to analyze DOM complexity and identify optimization opportunities, particularly useful for Webflow sites experiencing performance issues.

// Quick DOM Analysis Script - Run this in your browser console
(function analyzeDOMStructure() {
    console.clear();
    console.log('%cπŸ” DOM Analysis Starting...', 'font-size: 20px; color: #3498db; font-weight: bold;');
    
    const allNodes = document.querySelectorAll('*');
    const totalNodes = allNodes.length;
    
    // Collect data about each node
    const nodeData = Array.from(allNodes).map(node => {
        const children = node.children.length;
        const descendants = node.querySelectorAll('*').length;
        const classes = typeof node.className === 'string' 
            ? node.className.split(' ').filter(c => c) 
            : node.classList 
                ? Array.from(node.classList) 
                : [];
        
        return {
            element: node,
            tag: node.tagName,
            id: node.id,
            classes: classes,
            children: children,
            descendants: descendants,
            depth: getDepth(node),
            isWebflow: classes.some(c => c.startsWith('w-')),
            isCollection: classes.some(c => c.includes('collection')),
            isSlider: classes.some(c => c.includes('slider')),
            isNav: classes.some(c => c.includes('nav')),
        };
    });
    
    // Helper function to get element depth
    function getDepth(element) {
        let depth = 0;
        let current = element;
        while (current.parentElement) {
            depth++;
            current = current.parentElement;
        }
        return depth;
    }
    
    // Helper to create a selector path
    function getPath(element) {
        const path = [];
        let current = element;
        while (current && current !== document.body && path.length < 5) {
            let selector = current.tagName.toLowerCase();
            if (current.id) {
                selector += '#' + current.id;
            } else if (current.className) {
                const className = typeof current.className === 'string' 
                    ? current.className 
                    : (current.classList ? Array.from(current.classList).join(' ') : '');
                const classes = className.split(' ').filter(c => c).slice(0, 2);
                if (classes.length) selector += '.' + classes.join('.');
            }
            path.unshift(selector);
            current = current.parentElement;
        }
        return path.join(' > ');
    }
    
    // Sort by total descendants (heaviest elements)
    // Exclude structural elements that naturally contain everything
    const structuralTags = ['HTML', 'BODY', 'MAIN', 'HEAD', 'HEADER', 'FOOTER', 'SECTION', 'ARTICLE', 'ASIDE'];
    const heaviest = nodeData
        .filter(node => !structuralTags.includes(node.tag))
        .sort((a, b) => b.descendants - a.descendants)
        .slice(0, 30);
    
    // Find repeated patterns
    const repeatedPatterns = {};
    nodeData.forEach(node => {
        if (node.children > 5) {
            const firstChild = node.element.children[0];
            if (firstChild) {
                const firstChildClass = typeof firstChild.className === 'string' 
                    ? firstChild.className 
                    : (firstChild.classList ? Array.from(firstChild.classList).join(' ') : '');
                const similarChildren = Array.from(node.element.children)
                    .filter(child => {
                        const childClass = typeof child.className === 'string' 
                            ? child.className 
                            : (child.classList ? Array.from(child.classList).join(' ') : '');
                        return childClass === firstChildClass;
                    }).length;
                
                if (similarChildren / node.children > 0.8) {
                    const pattern = `${node.tag}.${node.classes.slice(0, 2).join('.')}`;
                    repeatedPatterns[pattern] = (repeatedPatterns[pattern] || 0) + 1;
                }
            }
        }
    });
    
    // Count tags
    const tagCounts = {};
    nodeData.forEach(node => {
        tagCounts[node.tag] = (tagCounts[node.tag] || 0) + 1;
    });
    
    // Webflow-specific analysis
    const webflowComponents = nodeData.filter(n => n.isWebflow);
    const collections = nodeData.filter(n => n.isCollection);
    const sliders = nodeData.filter(n => n.isSlider);
    const navs = nodeData.filter(n => n.isNav);
    
    // Display results
    console.log('\n%cπŸ“Š Summary', 'font-size: 16px; color: #2c3e50; font-weight: bold;');
    console.log(`Total DOM Nodes: ${totalNodes} ${totalNodes > 6000 ? '❌ (Over Google limit!)' : totalNodes > 1500 ? '⚠️ (High)' : 'βœ…'}`);
    console.log(`Google Recommended: 1,500 (You're ${totalNodes - 1500} over)`);
    console.log(`Google Upper Limit: 6,000 (You're ${totalNodes > 6000 ? (totalNodes - 6000) + ' over' : (6000 - totalNodes) + ' under'})`);
    
    console.log('\n%cπŸ‹οΈ Top 20 Heaviest Elements (excluding structural tags)', 'font-size: 16px; color: #e74c3c; font-weight: bold;');
    console.log('Note: Excluding HTML, BODY, MAIN, HEADER, FOOTER, SECTION, ARTICLE, ASIDE');
    console.table(heaviest.slice(0, 20).map(node => ({
        'Element': getPath(node.element),
        'Tag': node.tag,
        'Direct Children': node.children,
        'Total Descendants': node.descendants,
        'Depth': node.depth,
        '% of Total': ((node.descendants / totalNodes) * 100).toFixed(1) + '%'
    })));
    
    console.log('\n%cπŸ”„ Repeated Patterns (Potential for Optimization)', 'font-size: 16px; color: #f39c12; font-weight: bold;');
    console.table(Object.entries(repeatedPatterns)
        .sort((a, b) => b[1] - a[1])
        .slice(0, 10)
        .map(([pattern, count]) => ({
            'Pattern': pattern,
            'Instances': count,
            'Potential Impact': 'High repetition - consider virtualization or pagination'
        })));
    
    console.log('\n%c🏷️ Tag Distribution', 'font-size: 16px; color: #9b59b6; font-weight: bold;');
    const topTags = Object.entries(tagCounts)
        .sort((a, b) => b[1] - a[1])
        .slice(0, 10);
    console.table(topTags.map(([tag, count]) => ({
        'Tag': tag,
        'Count': count,
        '% of Total': ((count / totalNodes) * 100).toFixed(1) + '%'
    })));
    
    console.log('\n%c🎨 Webflow Components Analysis', 'font-size: 16px; color: #3498db; font-weight: bold;');
    console.log(`Webflow Components: ${webflowComponents.length}`);
    console.log(`Collection Lists: ${collections.length}`);
    console.log(`Sliders: ${sliders.length}`);
    console.log(`Navigation Elements: ${navs.length}`);
    
    // Highlight heavy elements
    console.log('\n%cπŸ’‘ Quick Actions', 'font-size: 16px; color: #27ae60; font-weight: bold;');
    console.log('1. Run this to highlight the heaviest elements:');
    console.log(`%chighlightHeavy()`, 'background: #2c3e50; color: white; padding: 4px 8px; border-radius: 4px;');
    console.log('2. Highlight specific number of elements:');
    console.log(`%chighlightHeavy(20)`, 'background: #2c3e50; color: white; padding: 4px 8px; border-radius: 4px;');
    
    window.highlightHeavy = function(count = 10) {
        // Remove previous highlights
        document.querySelectorAll('[data-dom-highlight]').forEach(el => {
            el.style.outline = '';
            el.style.backgroundColor = '';
            el.removeAttribute('data-dom-highlight');
        });
        
        // Highlight top N heaviest (excluding structural elements)
        const elementsToHighlight = Math.min(count, heaviest.length);
        heaviest.slice(0, elementsToHighlight).forEach((node, index) => {
            node.element.style.outline = `3px solid hsl(${(index * 360/elementsToHighlight)}, 70%, 50%)`;
            node.element.style.backgroundColor = `hsla(${(index * 360/elementsToHighlight)}, 70%, 50%, 0.1)`;
            node.element.setAttribute('data-dom-highlight', `heavy-${index + 1}`);
        });
        
        console.log(`βœ… Top ${elementsToHighlight} heaviest elements highlighted (excluding html/body/main)! Scroll to see them.`);
        return 'Highlighted';
    };
    
    // Recommendations
    console.log('\n%c🎯 Recommendations Based on Analysis', 'font-size: 16px; color: #16a085; font-weight: bold;');
    
    const recommendations = [];
    
    if (totalNodes > 6000) {
        recommendations.push('🚨 CRITICAL: Reduce DOM nodes below 6,000 for SEO compliance');
    }
    
    if (collections.length > 5) {
        recommendations.push('πŸ“š Consider limiting collection list items or implementing pagination');
    }
    
    if (Object.keys(repeatedPatterns).length > 10) {
        recommendations.push('♻️ Many repeated patterns detected - consider dynamic rendering or virtualization');
    }
    
    const deepestNode = Math.max(...nodeData.map(n => n.depth));
    if (deepestNode > 20) {
        recommendations.push(`πŸ—οΈ Very deep nesting detected (${deepestNode} levels) - flatten component structure`);
    }
    
    const divPercentage = ((tagCounts['DIV'] || 0) / totalNodes) * 100;
    if (divPercentage > 50) {
        recommendations.push(`πŸ“¦ ${divPercentage.toFixed(0)}% of elements are DIVs - use semantic HTML to reduce wrappers`);
    }
    
    if (sliders.length > 3) {
        recommendations.push('🎠 Multiple sliders detected - these are DOM-heavy, consider alternatives');
    }
    
    recommendations.forEach((rec, index) => {
        console.log(`${index + 1}. ${rec}`);
    });
    
    console.log('\n%c✨ Analysis Complete!', 'font-size: 18px; color: #2ecc71; font-weight: bold;');
    console.log('Tip: Click on any element in the tables above to inspect it in DevTools');
    console.log('Results saved to: window.domAnalysisResults');
    
    // Save results for later use
    window.domAnalysisResults = {
        total: totalNodes,
        heaviest: heaviest.slice(0, 20),
        patterns: repeatedPatterns,
        recommendations: recommendations,
        tagCounts: tagCounts,
        webflowComponents: {
            total: webflowComponents.length,
            collections: collections.length,
            sliders: sliders.length,
            navs: navs.length
        }
    };
    
    return window.domAnalysisResults;
})();

πŸš€ Quick Start

  1. Copy the entire script from the snippet below
  2. Open your website in Chrome/Firefox/Edge
  3. Open DevTools (F12 or right-click β†’ Inspect)
  4. Go to the Console tab
  5. Paste the script and press Enter
  6. View the comprehensive analysis in your console

πŸ“Š What It Analyzes

DOM Metrics

  • Total Node Count: Compares against Google's recommendations (1,500) and upper limit (6,000)
  • Node Distribution: Shows which elements contribute most to DOM bloat
  • Nesting Depth: Identifies overly deep component structures

Heavy Elements Detection

  • Lists the top 20 heaviest elements (by total descendants)
  • Excludes structural tags (HTML, BODY, MAIN, etc.) for actionable results
  • Shows percentage of total DOM each element represents
  • Provides exact element paths for easy identification

Pattern Recognition

  • Repeated Patterns: Identifies elements with many similar children (candidates for virtualization)
  • Webflow Components: Counts collection lists, sliders, navigation elements
  • Tag Distribution: Shows overuse of generic tags like DIVs

🎯 Key Features

Visual Highlighting

After running the analysis, use:

highlightHeavy()

This will:

  • Highlight the top 10 heaviest elements with different colors
  • Add semi-transparent backgrounds for easy identification
  • Exclude structural elements for actionable insights

Detailed Tables

The script outputs several formatted tables:

  1. Heaviest Elements - Shows path, children count, total descendants
  2. Repeated Patterns - Identifies optimization opportunities
  3. Tag Distribution - Reveals semantic HTML issues
  4. Webflow Components - Specific analysis for Webflow sites

Smart Recommendations

Based on your specific DOM structure, the script provides:

  • Critical alerts for SEO compliance
  • Specific optimization strategies
  • Performance improvement suggestions
  • Component refactoring advice

πŸ“ˆ Understanding the Results

Total DOM Nodes

  • < 1,500: βœ… Excellent (Google's recommendation)
  • 1,500 - 3,000: ⚠️ Acceptable but could be optimized
  • 3,000 - 6,000: ⚠️ High - will impact performance
  • > 6,000: ❌ Critical - exceeds Google's upper limit

Heavy Elements Analysis

Elements are ranked by total descendants (not just direct children). Focus on:

  • Elements with 500+ descendants
  • Repeated patterns with 10+ instances
  • Components taking >5% of total DOM

Common Issues & Solutions

Issue: Collection Lists with 100s of items

Solution: Implement pagination or lazy loading

Issue: Deep nesting (20+ levels)

Solution: Flatten component structure, reduce wrapper divs

Issue: 50%+ DIV elements

Solution: Use semantic HTML (article, section, nav, etc.)

Issue: Multiple heavy sliders

Solution: Consider lighter alternatives or load on-demand

πŸ› οΈ Advanced Usage

Export Data

To save the analysis for later:

// After running the main script
copy(JSON.stringify(window.domAnalysisResults, null, 2))

Custom Threshold

Modify the script to analyze different thresholds:

// Change line filtering repeated patterns
if (node.children > 5) { // Adjust this number

Focus on Specific Components

Filter results by Webflow components:

// After analysis, filter collection lists
window.domAnalysisResults.heaviest.filter(n => n.isCollection)

πŸ› Troubleshooting

"className.split is not a function" Error

This script handles SVG and other special elements correctly. If you encounter issues:

  • Ensure you're copying the complete script
  • Check for browser console errors before running

No Highlighting Visible

  • Some elements might be hidden or off-screen
  • Try scrolling after running highlightHeavy()
  • Check if CSS animations are interfering

Results Seem Wrong

  • Ensure the page is fully loaded before running
  • Dynamic content might load after analysis
  • Re-run after lazy-loaded content appears

πŸ’‘ Best Practices

  1. Run Multiple Times: If your site has dynamic content, run the analysis at different states
  2. Compare Pages: Run on different pages to identify template-level issues
  3. Before/After: Use to measure optimization impact
  4. Save Results: Keep benchmarks to track improvements

πŸ” Interpreting for Webflow

Webflow-Specific Insights

  • Collection Lists: Each can add 100s of nodes
  • Rich Text: Often creates deep nesting
  • Interactions: Hidden elements still count
  • Components: Reused components multiply DOM impact

When Webflow Says "Reduce DOM Nodes"

Use this script to prove whether the issue is:

  • Actually DOM-related (client-side rendering)
  • Server processing time (TTFB issues)
  • Component complexity in their editor

πŸ“š Further Reading

🀝 Contributing

Found an issue or have an improvement? This script is designed to be modified for your specific needs. Common modifications:

  • Add custom pattern detection
  • Filter by specific classes
  • Export to different formats
  • Integrate with performance monitoring

πŸ“ License

Free to use and modify. Created to help developers optimize their DOM structure and push back on oversimplified performance advice.


Remember: A high DOM count is often a symptom, not the cause. Use this tool to identify what to optimize, but don't forget to address server-side performance issues too.