Back to blog
InspectionJanuary 1, 2024ยท 9 min read

Mobile Responsiveness Testing: Is This Site Really Mobile-Friendly?

A practical Fusebox guide to mobile responsiveness testing.

Mobile Responsiveness Testing: Is This Site Really Mobile-Friendly?

Published: January 2024
Reading time: 8 minutes

Over 60% of web traffic is mobile. Yet many sites still break on phones and tablets. Here's how to detect responsive design issues, test mobile compatibility, and ensure sites work on every screen size.

Why Mobile Responsiveness Matters

  • Mobile traffic dominates - 60%+ of users are on phones
  • Google mobile-first indexing - Mobile version determines rankings
  • Conversion rates - 53% leave if site takes >3 seconds
  • User expectations - Seamless experience across devices
  • Competition - Users switch to mobile-friendly alternatives

Quick Mobile Responsiveness Check

1. Viewport and Basic Mobile Detection

// Quick mobile responsiveness audit
(function mobileCheck() {
  console.log('๐Ÿ“ฑ Mobile Responsiveness Check\n');
  
  // 1. Check viewport meta tag
  const viewport = document.querySelector('meta[name="viewport"]');
  if (viewport) {
    console.log('โœ… Viewport meta tag found:', viewport.content);
    
    // Check for proper configuration
    const content = viewport.content;
    const issues = [];
    
    if (!content.includes('width=device-width')) {
      issues.push('Missing width=device-width');
    }
    if (!content.includes('initial-scale=1')) {
      issues.push('Missing initial-scale=1');
    }
    if (content.includes('maximum-scale=1')) {
      issues.push('Zoom disabled (accessibility issue)');
    }
    if (content.includes('user-scalable=no')) {
      issues.push('Zoom disabled (accessibility issue)');
    }
    
    if (issues.length > 0) {
      console.log('โš ๏ธ Viewport issues:', issues.join(', '));
    }
  } else {
    console.log('โŒ No viewport meta tag! Site not mobile-optimized');
  }
  
  // 2. Check current viewport
  console.log(`\n๐Ÿ“ Current Viewport:`);
  console.log(`Width: ${window.innerWidth}px`);
  console.log(`Height: ${window.innerHeight}px`);
  console.log(`Device Pixel Ratio: ${window.devicePixelRatio}`);
  console.log(`Orientation: ${window.innerWidth > window.innerHeight ? 'Landscape' : 'Portrait'}`);
  
  // 3. Check if actually on mobile
  const isMobile = /Mobile|Android|iPhone|iPad/i.test(navigator.userAgent);
  const hasTouch = 'ontouchstart' in window;
  console.log(`\n๐Ÿ“ฑ Device Detection:`);
  console.log(`Mobile User Agent: ${isMobile ? 'Yes' : 'No'}`);
  console.log(`Touch Support: ${hasTouch ? 'Yes' : 'No'}`);
})();

2. Responsive Design Analysis

// Analyze responsive design implementation
function analyzeResponsiveDesign() {
  console.log('\n๐ŸŽจ Responsive Design Analysis:');
  
  // 1. Check for media queries
  const stylesheets = Array.from(document.styleSheets);
  let mediaQueries = [];
  
  stylesheets.forEach(sheet => {
    try {
      const rules = Array.from(sheet.cssRules || []);
      rules.forEach(rule => {
        if (rule.type === CSSRule.MEDIA_RULE) {
          mediaQueries.push(rule.media.mediaText);
        }
      });
    } catch(e) {
      // Cross-origin stylesheets
    }
  });
  
  if (mediaQueries.length > 0) {
    console.log(`โœ… Found ${mediaQueries.length} media queries`);
    
    // Analyze breakpoints
    const breakpoints = new Set();
    mediaQueries.forEach(query => {
      const widthMatch = query.match(/(?:min|max)-width:\s*(\d+)px/);
      if (widthMatch) {
        breakpoints.add(parseInt(widthMatch[1]));
      }
    });
    
    console.log('Breakpoints detected:', Array.from(breakpoints).sort((a, b) => a - b));
  } else {
    console.log('โŒ No media queries found - not responsive?');
  }
  
  // 2. Check for responsive units
  const computedStyles = window.getComputedStyle(document.body);
  const responsiveUnits = {
    'Viewport units (vw/vh)': false,
    'Relative units (rem/em)': false,
    'Percentage widths': false,
    'Flexible images': false
  };
  
  // Sample elements for responsive units
  document.querySelectorAll('*').forEach(el => {
    const styles = window.getComputedStyle(el);
    
    if (styles.width.includes('vw') || styles.height.includes('vh')) {
      responsiveUnits['Viewport units (vw/vh)'] = true;
    }
    if (styles.fontSize.includes('rem') || styles.fontSize.includes('em')) {
      responsiveUnits['Relative units (rem/em)'] = true;
    }
    if (styles.width.includes('%')) {
      responsiveUnits['Percentage widths'] = true;
    }
  });
  
  // Check images
  const images = Array.from(document.images);
  images.forEach(img => {
    const styles = window.getComputedStyle(img);
    if (styles.maxWidth === '100%' || styles.width.includes('%')) {
      responsiveUnits['Flexible images'] = true;
    }
  });
  
  console.log('\n๐Ÿ“ Responsive Units:');
  Object.entries(responsiveUnits).forEach(([unit, used]) => {
    console.log(`${used ? 'โœ…' : 'โŒ'} ${unit}`);
  });
}

analyzeResponsiveDesign();

3. Mobile Performance Check

// Check mobile-specific performance issues
function checkMobilePerformance() {
  console.log('\nโšก Mobile Performance Check:');
  
  // 1. Check resource sizes
  const resources = performance.getEntriesByType('resource');
  let totalSize = 0;
  let issues = [];
  
  resources.forEach(resource => {
    totalSize += resource.transferSize || 0;
    
    // Flag large resources
    if (resource.transferSize > 500000) { // 500KB
      issues.push({
        url: resource.name.split('/').pop(),
        size: `${(resource.transferSize / 1024).toFixed(0)}KB`,
        type: resource.initiatorType
      });
    }
  });
  
  console.log(`Total page size: ${(totalSize / 1024 / 1024).toFixed(2)}MB`);
  
  if (totalSize > 5 * 1024 * 1024) {
    console.log('โš ๏ธ Page too large for mobile (>5MB)');
  }
  
  if (issues.length > 0) {
    console.log('\nโŒ Large resources detected:');
    console.table(issues);
  }
  
  // 2. Check for mobile-unfriendly features
  const mobileIssues = {
    'Hover-only interactions': document.querySelectorAll(':hover').length > 10,
    'Small touch targets': false,
    'Horizontal scrolling': document.body.scrollWidth > window.innerWidth,
    'Fixed positioning overuse': document.querySelectorAll('[style*="position: fixed"]').length > 3,
    'Auto-playing videos': document.querySelectorAll('video[autoplay]').length > 0
  };
  
  // Check touch target sizes
  const clickables = document.querySelectorAll('a, button, input, select, textarea');
  let smallTargets = 0;
  clickables.forEach(el => {
    const rect = el.getBoundingClientRect();
    if (rect.width < 44 || rect.height < 44) {
      smallTargets++;
    }
  });
  
  if (smallTargets > 0) {
    mobileIssues['Small touch targets'] = true;
  }
  
  console.log('\n๐Ÿ“ฑ Mobile-Specific Issues:');
  Object.entries(mobileIssues).forEach(([issue, found]) => {
    console.log(`${found ? 'โŒ' : 'โœ…'} ${issue}`);
  });
}

checkMobilePerformance();

Testing Different Screen Sizes

1. Responsive Breakpoint Tester

// Test how site looks at different breakpoints
function testBreakpoints() {
  const commonBreakpoints = [
    { name: 'Mobile S', width: 320 },
    { name: 'Mobile M', width: 375 },
    { name: 'Mobile L', width: 425 },
    { name: 'Tablet', width: 768 },
    { name: 'Laptop', width: 1024 },
    { name: 'Desktop', width: 1440 }
  ];
  
  console.log('\n๐Ÿ“ฑ Breakpoint Analysis:');
  console.log('(Resize browser to test each breakpoint)\n');
  
  commonBreakpoints.forEach(device => {
    console.log(`${device.name} (${device.width}px):`);
    
    // Check what would happen at this width
    const ratio = device.width / window.innerWidth;
    
    if (ratio < 0.5) {
      console.log('  ๐Ÿ“ฑ Mobile layout likely');
    } else if (ratio < 0.8) {
      console.log('  ๐Ÿ“ฑ Tablet layout likely');
    } else {
      console.log('  ๐Ÿ–ฅ๏ธ Desktop layout likely');
    }
  });
  
  // Current responsive state
  const currentWidth = window.innerWidth;
  let currentLayout = 'Desktop';
  
  if (currentWidth < 768) currentLayout = 'Mobile';
  else if (currentWidth < 1024) currentLayout = 'Tablet';
  
  console.log(`\nCurrent layout: ${currentLayout} (${currentWidth}px)`);
}

testBreakpoints();

2. Element Overflow Detection

// Find elements causing horizontal scroll
function detectOverflow() {
  console.log('\n๐Ÿ“ Overflow Detection:');
  
  const docWidth = document.documentElement.offsetWidth;
  const overflowing = [];
  
  document.querySelectorAll('*').forEach(el => {
    const rect = el.getBoundingClientRect();
    
    if (rect.right > docWidth || rect.left < 0) {
      overflowing.push({
        element: el.tagName + (el.className ? `.${el.className.split(' ')[0]}` : ''),
        left: Math.round(rect.left),
        right: Math.round(rect.right),
        width: Math.round(rect.width)
      });
    }
  });
  
  if (overflowing.length > 0) {
    console.log('โŒ Elements causing horizontal scroll:');
    console.table(overflowing);
  } else {
    console.log('โœ… No horizontal overflow detected');
  }
  
  // Check body scroll
  if (document.body.scrollWidth > window.innerWidth) {
    console.log(`\nโš ๏ธ Page has horizontal scroll!`);
    console.log(`Page width: ${document.body.scrollWidth}px`);
    console.log(`Viewport width: ${window.innerWidth}px`);
  }
}

detectOverflow();

Touch and Gesture Support

1. Touch Capability Detection

// Comprehensive touch support check
function checkTouchSupport() {
  console.log('\n๐Ÿ‘† Touch & Gesture Support:');
  
  const touchTests = {
    'Touch Events': 'ontouchstart' in window,
    'Pointer Events': 'PointerEvent' in window,
    'Max Touch Points': navigator.maxTouchPoints > 0,
    'Coarse Pointer': window.matchMedia('(pointer: coarse)').matches,
    'Hover Capability': window.matchMedia('(hover: hover)').matches
  };
  
  Object.entries(touchTests).forEach(([test, result]) => {
    console.log(`${result ? 'โœ…' : 'โŒ'} ${test}`);
  });
  
  // Check for touch event listeners
  const touchHandlers = {
    touchstart: 0,
    touchmove: 0,
    touchend: 0,
    click: 0
  };
  
  // This is approximate - checking for potential handlers
  const scripts = Array.from(document.scripts);
  scripts.forEach(script => {
    if (script.textContent) {
      Object.keys(touchHandlers).forEach(event => {
        if (script.textContent.includes(`addEventListener('${event}'`) ||
            script.textContent.includes(`on${event}`)) {
          touchHandlers[event]++;
        }
      });
    }
  });
  
  console.log('\n๐Ÿ“ฑ Touch Handler Detection:');
  Object.entries(touchHandlers).forEach(([event, count]) => {
    if (count > 0) {
      console.log(`โœ… ${event} handlers found`);
    }
  });
}

checkTouchSupport();

2. Gesture and Interaction Analysis

// Check for mobile-friendly interactions
function analyzeInteractions() {
  console.log('\n๐Ÿคš Interaction Analysis:');
  
  // Check for problematic patterns
  const issues = [];
  
  // Small clickable areas
  const links = document.querySelectorAll('a, button');
  let smallTargets = 0;
  
  links.forEach(link => {
    const rect = link.getBoundingClientRect();
    const area = rect.width * rect.height;
    
    if (area > 0 && (rect.width < 44 || rect.height < 44)) {
      smallTargets++;
    }
  });
  
  if (smallTargets > 0) {
    issues.push(`${smallTargets} touch targets smaller than 44x44px`);
  }
  
  // Links too close together
  let closeLinkPairs = 0;
  for (let i = 0; i < links.length - 1; i++) {
    const rect1 = links[i].getBoundingClientRect();
    const rect2 = links[i + 1].getBoundingClientRect();
    
    const distance = Math.sqrt(
      Math.pow(rect2.left - rect1.right, 2) + 
      Math.pow(rect2.top - rect1.top, 2)
    );
    
    if (distance < 8) {
      closeLinkPairs++;
    }
  }
  
  if (closeLinkPairs > 0) {
    issues.push(`${closeLinkPairs} link pairs too close together`);
  }
  
  // Hover-dependent elements
  const hoverRules = Array.from(document.styleSheets).reduce((count, sheet) => {
    try {
      const rules = Array.from(sheet.cssRules || []);
      return count + rules.filter(r => r.selectorText?.includes(':hover')).length;
    } catch {
      return count;
    }
  }, 0);
  
  if (hoverRules > 20) {
    issues.push(`Heavy hover dependency (${hoverRules} :hover rules)`);
  }
  
  if (issues.length > 0) {
    console.log('โš ๏ธ Mobile interaction issues:');
    issues.forEach(issue => console.log(`  - ${issue}`));
  } else {
    console.log('โœ… No major interaction issues detected');
  }
}

analyzeInteractions();

Real-World Mobile Issues

1. Common Mobile Problems Detection

// Detect common mobile UX problems
function detectCommonMobileIssues() {
  console.log('\n๐Ÿšจ Common Mobile Issues:');
  
  const issues = {
    // Text readability
    'Small font size': Array.from(document.querySelectorAll('p, span, div')).some(el => {
      const fontSize = parseInt(window.getComputedStyle(el).fontSize);
      return fontSize < 14;
    }),
    
    // Form issues
    'Missing input types': Array.from(document.querySelectorAll('input')).some(input => {
      return (input.type === 'text' && 
              (input.name?.includes('email') || input.placeholder?.includes('email')));
    }),
    
    // Images
    'Non-responsive images': Array.from(document.images).some(img => {
      const styles = window.getComputedStyle(img);
      return styles.maxWidth !== '100%' && !img.srcset;
    }),
    
    // Tables
    'Non-responsive tables': document.querySelectorAll('table').length > 0 && 
      !document.querySelector('div[style*="overflow"]'),
    
    // Pop-ups
    'Intrusive interstitials': document.querySelectorAll('[class*="modal"], [class*="popup"]').length > 0,
    
    // Fixed elements
    'Too many fixed elements': document.querySelectorAll('[style*="position: fixed"]').length > 2
  };
  
  Object.entries(issues).forEach(([issue, found]) => {
    console.log(`${found ? 'โŒ' : 'โœ…'} ${issue}`);
  });
}

detectCommonMobileIssues();

2. iOS/Android Specific Checks

// Platform-specific mobile checks
function checkPlatformSpecific() {
  const ua = navigator.userAgent;
  
  if (/iPhone|iPad/.test(ua)) {
    console.log('\n๐ŸŽ iOS Specific Checks:');
    
    // iOS viewport
    const viewport = document.querySelector('meta[name="viewport"]');
    if (viewport && !viewport.content.includes('viewport-fit=cover')) {
      console.log('โš ๏ธ Missing viewport-fit=cover for iPhone X+ safe areas');
    }
    
    // iOS web app capable
    const webAppCapable = document.querySelector('meta[name="apple-mobile-web-app-capable"]');
    console.log(`Web App Capable: ${webAppCapable ? 'โœ…' : 'โŒ'}`);
    
    // Status bar style
    const statusBar = document.querySelector('meta[name="apple-mobile-web-app-status-bar-style"]');
    console.log(`Status Bar Style: ${statusBar ? statusBar.content : 'Not set'}`);
    
    // Touch icons
    const touchIcon = document.querySelector('link[rel="apple-touch-icon"]');
    console.log(`Touch Icon: ${touchIcon ? 'โœ…' : 'โŒ'}`);
    
  } else if (/Android/.test(ua)) {
    console.log('\n๐Ÿค– Android Specific Checks:');
    
    // Theme color
    const themeColor = document.querySelector('meta[name="theme-color"]');
    console.log(`Theme Color: ${themeColor ? themeColor.content : 'Not set'}`);
    
    // Web app manifest
    const manifest = document.querySelector('link[rel="manifest"]');
    console.log(`Web App Manifest: ${manifest ? 'โœ…' : 'โŒ'}`);
  }
}

checkPlatformSpecific();

Mobile SEO Check

Mobile-First Indexing Readiness

// Check if site is ready for mobile-first indexing
function checkMobileSEO() {
  console.log('\n๐Ÿ” Mobile SEO Check:');
  
  const seoChecks = {
    'Viewport tag': !!document.querySelector('meta[name="viewport"]'),
    'Mobile-friendly content': window.innerWidth < 400 ? 
      document.body.scrollWidth <= window.innerWidth : true,
    'Readable font size': !Array.from(document.querySelectorAll('p')).some(p => {
      return parseInt(window.getComputedStyle(p).fontSize) < 12;
    }),
    'Structured data': !!document.querySelector('script[type="application/ld+json"]'),
    'Alt text on images': Array.from(document.images).every(img => img.alt),
    'Lazy loading': Array.from(document.images).some(img => img.loading === 'lazy')
  };
  
  let score = 0;
  Object.entries(seoChecks).forEach(([check, passed]) => {
    console.log(`${passed ? 'โœ…' : 'โŒ'} ${check}`);
    if (passed) score++;
  });
  
  const percentage = (score / Object.keys(seoChecks).length * 100).toFixed(0);
  console.log(`\nMobile SEO Score: ${percentage}%`);
  
  if (percentage < 80) {
    console.log('โš ๏ธ Improvements needed for mobile-first indexing');
  }
}

checkMobileSEO();

Complete Mobile Audit

// Run comprehensive mobile audit
(function completeMobileAudit() {
  console.log('๐Ÿ“ฑ Complete Mobile Responsiveness Audit\n');
  console.log('โ•'.repeat(50));
  
  // Run all checks
  mobileCheck();
  analyzeResponsiveDesign();
  checkMobilePerformance();
  detectOverflow();
  checkTouchSupport();
  analyzeInteractions();
  detectCommonMobileIssues();
  checkPlatformSpecific();
  checkMobileSEO();
  
  // Summary
  console.log('\n๐Ÿ“Š Mobile Readiness Summary:');
  console.log('โ•'.repeat(50));
  
  const summary = {
    'Has Viewport': !!document.querySelector('meta[name="viewport"]'),
    'No Horizontal Scroll': document.body.scrollWidth <= window.innerWidth,
    'Touch Optimized': navigator.maxTouchPoints > 0 || 'ontouchstart' in window,
    'Fast Loading': performance.timing.loadEventEnd - performance.timing.navigationStart < 3000,
    'Mobile SEO Ready': !!document.querySelector('meta[name="viewport"]') && 
                       !document.querySelector('meta[name="viewport"]').content.includes('maximum-scale=1')
  };
  
  let passed = 0;
  Object.entries(summary).forEach(([check, result]) => {
    console.log(`${result ? 'โœ…' : 'โŒ'} ${check}`);
    if (result) passed++;
  });
  
  const score = (passed / Object.keys(summary).length * 100).toFixed(0);
  console.log(`\nOverall Mobile Score: ${score}%`);
  
  if (score === 100) {
    console.log('๐ŸŽ‰ Excellent mobile optimization!');
  } else if (score >= 80) {
    console.log('๐Ÿ‘ Good mobile experience with minor improvements needed');
  } else if (score >= 60) {
    console.log('โš ๏ธ Significant mobile improvements needed');
  } else {
    console.log('โŒ Poor mobile experience - major work required');
  }
})();

The Bottom Line

Mobile responsiveness is non-negotiable:

  • Most users are mobile - Design mobile-first
  • Google ranks mobile version - SEO depends on it
  • Users expect perfection - Smooth, fast, touch-friendly
  • Competition is mobile-ready - Don't fall behind
  • It's not just small screens - Touch, performance, interactions

Test on real devices. Fix mobile issues first. Desktop is the enhancement.


Test mobile responsiveness instantly: Fusebox analyzes responsive design, mobile performance, and touch optimization while you browse. Ensure perfect mobile experiences. $29 one-time purchase.