Legacy Browser Support Strategies
1. Internet Explorer 11 Polyfill Requirements
| Feature Category | Missing in IE11 | Polyfill Package | Size Impact |
|---|---|---|---|
| ES6 Core | Promise, Symbol, WeakMap, Set | core-js/es | ~40 KB |
| Array Methods | find, findIndex, includes, from | core-js/features/array | ~5 KB |
| Object Methods | assign, entries, values, keys | core-js/features/object | ~3 KB |
| String Methods | includes, startsWith, endsWith | core-js/features/string | ~2 KB |
| Web APIs | fetch, IntersectionObserver, CustomEvent | whatwg-fetch, polyfill packages | ~15 KB |
| DOM APIs | classList, dataset, matches | Custom polyfills | ~3 KB |
| CSS Features | CSS variables, Grid, Flexbox bugs | css-vars-ponyfill, Autoprefixer | Variable |
Example: Complete IE11 polyfill bundle
// polyfills/ie11.js - Comprehensive IE11 support
// 1. Core ES6 features
import 'core-js/es/symbol';
import 'core-js/es/promise';
import 'core-js/es/map';
import 'core-js/es/set';
import 'core-js/es/weak-map';
import 'core-js/es/weak-set';
// 2. Array methods
import 'core-js/es/array/find';
import 'core-js/es/array/find-index';
import 'core-js/es/array/includes';
import 'core-js/es/array/from';
import 'core-js/es/array/of';
import 'core-js/es/array/fill';
import 'core-js/es/array/iterator';
// 3. Object methods
import 'core-js/es/object/assign';
import 'core-js/es/object/keys';
import 'core-js/es/object/values';
import 'core-js/es/object/entries';
import 'core-js/es/object/get-own-property-descriptors';
// 4. String methods
import 'core-js/es/string/includes';
import 'core-js/es/string/starts-with';
import 'core-js/es/string/ends-with';
import 'core-js/es/string/repeat';
import 'core-js/es/string/pad-start';
import 'core-js/es/string/pad-end';
// 5. Number methods
import 'core-js/es/number/is-nan';
import 'core-js/es/number/is-finite';
import 'core-js/es/number/is-integer';
// 6. Web APIs
import 'whatwg-fetch';
import 'abortcontroller-polyfill';
import 'url-search-params-polyfill';
// 7. DOM APIs
// CustomEvent
if (typeof window.CustomEvent !== 'function') {
window.CustomEvent = function(event, params) {
params = params || { bubbles: false, cancelable: false, detail: null };
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
};
}
// Element.matches
if (!Element.prototype.matches) {
Element.prototype.matches =
Element.prototype.msMatchesSelector ||
Element.prototype.webkitMatchesSelector;
}
// Element.closest
if (!Element.prototype.closest) {
Element.prototype.closest = function(selector) {
var el = this;
while (el) {
if (el.matches(selector)) {
return el;
}
el = el.parentElement;
}
return null;
};
}
// classList (IE11 has partial support)
if (!('classList' in document.documentElement) ||
!document.documentElement.classList ||
!document.documentElement.classList.toggle('test', false)) {
// Full classList polyfill for IE9-10
// (IE11 has it but toggle() is buggy)
}
// NodeList.forEach
if (window.NodeList && !NodeList.prototype.forEach) {
NodeList.prototype.forEach = Array.prototype.forEach;
}
// 8. requestAnimationFrame
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] ||
window[vendors[x] + 'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = function(callback) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() {
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
}
if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}
})();
// 9. Console polyfill (IE9-10)
if (!window.console) {
window.console = {
log: function() {},
warn: function() {},
error: function() {},
info: function() {},
debug: function() {}
};
}
// 10. Object.setPrototypeOf
if (!Object.setPrototypeOf) {
Object.setPrototypeOf = function(obj, proto) {
obj.__proto__ = proto;
return obj;
};
}
console.log('IE11 polyfills loaded');
Example: IE11-specific Babel configuration
// babel.config.js for IE11
module.exports = {
presets: [
['@babel/preset-env', {
targets: {
ie: '11'
},
useBuiltIns: 'usage',
corejs: {
version: 3,
proposals: false
},
// Important: Don't use loose mode for IE11
loose: false,
// Force all transforms
forceAllTransforms: false,
// Exclude modern features IE11 will never support
exclude: [
'transform-async-to-generator',
'transform-regenerator'
]
}]
],
plugins: [
// Transform async/await to Promise chains
['@babel/plugin-transform-runtime', {
corejs: 3,
helpers: true,
regenerator: true
}],
// Transform for..of loops
'@babel/plugin-transform-for-of'
]
};
// webpack.config.js for IE11
module.exports = {
target: ['web', 'es5'],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules\/(?!(problematic-package)\/).*/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true
}
}
}
]
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
ecma: 5,
ie8: false,
safari10: false
}
})
]
}
};
Warning: IE11 requires ~80-100 KB of polyfills for full ES6
support. Consider dropping IE11 support or providing a minimal experience.
2. Safari and WebKit Specific Polyfills
| Safari Version | Missing Features | Polyfill | Notes |
|---|---|---|---|
| Safari 10-11 | IntersectionObserver, ResizeObserver | intersection-observer, resize-observer-polyfill | Common mobile Safari |
| Safari 12 | Intl.RelativeTimeFormat, BigInt | @formatjs/intl-relativetimeformat | Partial Intl support |
| Safari 13 | Promise.allSettled, String.matchAll | core-js/features | Some ES2020 features |
| Safari 14 | Logical assignment operators | Babel transform | ??=, ||=, &&= |
| iOS Safari | Date parsing inconsistencies | Custom date parsing | YYYY-MM-DD format |
Example: Safari-specific polyfills
// polyfills/safari.js
// 1. Feature detection for Safari
var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
var safariVersion = (function() {
var match = navigator.userAgent.match(/Version\/(\d+)/);
return match ? parseInt(match[1]) : null;
})();
console.log('Safari detected:', isSafari, 'Version:', safariVersion);
// 2. IntersectionObserver (Safari < 12.1)
if (!('IntersectionObserver' in window)) {
import('intersection-observer').then(() => {
console.log('IntersectionObserver polyfilled');
});
}
// 3. ResizeObserver (Safari < 13.1)
if (!('ResizeObserver' in window)) {
import('resize-observer-polyfill').then((module) => {
window.ResizeObserver = module.default;
console.log('ResizeObserver polyfilled');
});
}
// 4. Promise.allSettled (Safari < 13)
if (!Promise.allSettled) {
Promise.allSettled = function(promises) {
return Promise.all(
promises.map(function(promise) {
return Promise.resolve(promise).then(
function(value) {
return { status: 'fulfilled', value: value };
},
function(reason) {
return { status: 'rejected', reason: reason };
}
);
})
);
};
}
// 5. Safari Date parsing fix
var OriginalDate = Date;
var SafeDate = function() {
if (arguments.length === 1 && typeof arguments[0] === 'string') {
var dateString = arguments[0];
// Fix YYYY-MM-DD format (Safari treats as UTC, others as local)
if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
dateString = dateString + 'T00:00:00';
}
return new OriginalDate(dateString);
}
return new (Function.prototype.bind.apply(
OriginalDate,
[null].concat(Array.prototype.slice.call(arguments))
));
};
SafeDate.prototype = OriginalDate.prototype;
SafeDate.now = OriginalDate.now;
SafeDate.UTC = OriginalDate.UTC;
SafeDate.parse = OriginalDate.parse;
if (isSafari) {
window.Date = SafeDate;
}
// 6. Intl.RelativeTimeFormat (Safari < 14)
if (!Intl.RelativeTimeFormat) {
import('@formatjs/intl-relativetimeformat').then(() => {
import('@formatjs/intl-relativetimeformat/locale-data/en').then(() => {
console.log('Intl.RelativeTimeFormat polyfilled');
});
});
}
// 7. scrollIntoView smooth behavior (Safari < 15.4)
if (!('scrollBehavior' in document.documentElement.style)) {
import('smoothscroll-polyfill').then((smoothscroll) => {
smoothscroll.polyfill();
console.log('Smooth scroll polyfilled');
});
}
// 8. Web Animations API (Safari < 13.1)
if (!Element.prototype.animate) {
import('web-animations-js').then(() => {
console.log('Web Animations API polyfilled');
});
}
// 9. Safari-specific CSS fixes
if (isSafari) {
// Add Safari-specific class for CSS targeting
document.documentElement.classList.add('safari');
if (safariVersion) {
document.documentElement.classList.add('safari-' + safariVersion);
}
}
Note: Safari often lags 1-2 years behind Chrome in feature
support. Focus on polyfilling Observers and modern Promise methods for iOS Safari.
3. Android WebView and Mobile Browser Support
| Platform | Common Issues | Polyfill Strategy | Considerations |
|---|---|---|---|
| Android 4.4 WebView | ES6 features, Promise, fetch | Full core-js + fetch | Still ~10% of Android users |
| Chrome Android | Usually up-to-date | Minimal polyfills | Auto-updates enabled |
| Samsung Internet | Lags behind Chrome | Target Chrome - 2 versions | Popular in Asia |
| UC Browser | Various compatibility issues | Conservative polyfills | Popular in China/India |
| Mobile Safari iOS | Same as Safari desktop | Safari polyfills | Tied to iOS version |
Example: Mobile browser detection and polyfills
// mobile-polyfills.js
var MobilePolyfills = {
// Detect mobile browser
detect: function() {
var ua = navigator.userAgent;
return {
isAndroid: /Android/i.test(ua),
isIOS: /iPhone|iPad|iPod/i.test(ua),
isChromeAndroid: /Chrome/i.test(ua) && /Android/i.test(ua),
isSamsungInternet: /SamsungBrowser/i.test(ua),
isUCBrowser: /UCBrowser/i.test(ua),
isMobileSafari: /Safari/i.test(ua) && /iPhone|iPad|iPod/i.test(ua),
// Get Android version
androidVersion: (function() {
var match = ua.match(/Android\s+([\d.]+)/);
return match ? parseFloat(match[1]) : null;
})(),
// Get iOS version
iosVersion: (function() {
var match = ua.match(/OS\s+([\d_]+)/);
if (match) {
return parseFloat(match[1].replace(/_/g, '.'));
}
return null;
})(),
// Check if WebView
isWebView: (function() {
return /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(ua) ||
/wv/.test(ua);
})()
};
},
// Load appropriate polyfills
load: function() {
var device = this.detect();
var polyfills = [];
console.log('Mobile device detected:', device);
// Old Android (4.4 and below)
if (device.isAndroid && device.androidVersion && device.androidVersion < 5) {
console.log('Old Android detected, loading full polyfills');
polyfills.push(
import('core-js/stable'),
import('regenerator-runtime/runtime'),
import('whatwg-fetch')
);
}
// Android 5-7 (missing some ES6+ features)
if (device.isAndroid && device.androidVersion &&
device.androidVersion >= 5 && device.androidVersion < 8) {
polyfills.push(
import('core-js/features/promise'),
import('core-js/features/array/includes'),
import('core-js/features/object/assign')
);
}
// Samsung Internet
if (device.isSamsungInternet) {
console.log('Samsung Internet detected');
// Samsung Internet lags ~6 months behind Chrome
if (!window.IntersectionObserver) {
polyfills.push(import('intersection-observer'));
}
}
// UC Browser
if (device.isUCBrowser) {
console.log('UC Browser detected');
// UC Browser has various quirks, load conservative polyfills
polyfills.push(
import('core-js/features/promise'),
import('whatwg-fetch')
);
}
// iOS Safari
if (device.isMobileSafari || (device.isIOS && device.isWebView)) {
console.log('iOS Safari/WebView detected, version:', device.iosVersion);
// iOS 12 and below
if (device.iosVersion && device.iosVersion < 13) {
if (!window.IntersectionObserver) {
polyfills.push(import('intersection-observer'));
}
if (!Promise.allSettled) {
polyfills.push(import('core-js/features/promise/all-settled'));
}
}
}
// Viewport units fix for mobile browsers
if (device.isIOS || device.isAndroid) {
this.fixViewportUnits();
}
// Touch event polyfills
if (!('ontouchstart' in window)) {
this.polyfillTouchEvents();
}
return Promise.all(polyfills).then(function() {
console.log('Mobile polyfills loaded');
});
},
// Fix viewport units on mobile
fixViewportUnits: function() {
// Fix for iOS Safari vh units including address bar
var setVh = function() {
var vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', vh + 'px');
};
setVh();
window.addEventListener('resize', setVh);
window.addEventListener('orientationchange', setVh);
},
// Polyfill touch events for desktop testing
polyfillTouchEvents: function() {
console.log('Polyfilling touch events');
// Map mouse events to touch events
var touchMap = {
'mousedown': 'touchstart',
'mousemove': 'touchmove',
'mouseup': 'touchend'
};
Object.keys(touchMap).forEach(function(mouseEvent) {
document.addEventListener(mouseEvent, function(e) {
var touch = {
identifier: Date.now(),
target: e.target,
clientX: e.clientX,
clientY: e.clientY,
pageX: e.pageX,
pageY: e.pageY,
screenX: e.screenX,
screenY: e.screenY
};
var touchEvent = new CustomEvent(touchMap[mouseEvent], {
bubbles: true,
cancelable: true,
detail: {
touches: [touch],
targetTouches: [touch],
changedTouches: [touch]
}
});
e.target.dispatchEvent(touchEvent);
});
});
}
};
// Initialize mobile polyfills
MobilePolyfills.load().then(function() {
console.log('Mobile app ready');
initApp();
});
Warning: Android 4.4 WebView (still ~8% of users in developing
markets) requires extensive polyfilling. Consider separate build or basic fallback.
4. Progressive Enhancement with Polyfills
| Layer | Implementation | Fallback | User Experience |
|---|---|---|---|
| Core Functionality | Works without JS/polyfills | Server-side rendering | Basic but functional |
| Enhanced UI | Modern JS features | Polyfills load on-demand | Rich interactions |
| Advanced Features | Cutting-edge APIs | Feature detection, graceful skip | Optional enhancements |
| Performance | Modern browser optimizations | Fallback to simpler code | Fast for all |
Example: Progressive enhancement pattern
// progressive-enhancement.js
var ProgressiveEnhancement = {
// Feature tiers
tiers: {
core: {
name: 'Core Experience',
required: [],
features: ['basicDOM', 'events']
},
enhanced: {
name: 'Enhanced Experience',
required: ['Promise', 'fetch', 'Object.assign'],
features: ['asyncData', 'modernUI']
},
advanced: {
name: 'Advanced Experience',
required: [
'IntersectionObserver',
'ResizeObserver',
'CustomElements'
],
features: ['lazyLoading', 'animations', 'webComponents']
}
},
// Detect available tier
detectTier: function() {
var hasEnhanced = this.tiers.enhanced.required.every(function(feature) {
return this.hasFeature(feature);
}.bind(this));
var hasAdvanced = this.tiers.advanced.required.every(function(feature) {
return this.hasFeature(feature);
}.bind(this));
if (hasAdvanced) {
return 'advanced';
} else if (hasEnhanced) {
return 'enhanced';
} else {
return 'core';
}
},
// Check feature availability
hasFeature: function(featureName) {
var checks = {
'Promise': function() {
return typeof Promise !== 'undefined';
},
'fetch': function() {
return typeof fetch !== 'undefined';
},
'Object.assign': function() {
return typeof Object.assign === 'function';
},
'IntersectionObserver': function() {
return typeof IntersectionObserver !== 'undefined';
},
'ResizeObserver': function() {
return typeof ResizeObserver !== 'undefined';
},
'CustomElements': function() {
return typeof customElements !== 'undefined';
}
};
return checks[featureName] ? checks[featureName]() : false;
},
// Load polyfills to upgrade tier
upgradeTier: function(targetTier) {
var currentTier = this.detectTier();
console.log('Current tier:', currentTier);
console.log('Target tier:', targetTier);
if (currentTier === targetTier) {
return Promise.resolve(currentTier);
}
var polyfills = [];
var required = this.tiers[targetTier].required;
required.forEach(function(feature) {
if (!this.hasFeature(feature)) {
polyfills.push(this.loadPolyfillFor(feature));
}
}.bind(this));
return Promise.all(polyfills).then(function() {
console.log('Upgraded to:', targetTier);
return targetTier;
});
},
// Load polyfill for specific feature
loadPolyfillFor: function(feature) {
var polyfillMap = {
'Promise': function() {
return import('es6-promise/auto');
},
'fetch': function() {
return import('whatwg-fetch');
},
'Object.assign': function() {
return import('core-js/features/object/assign');
},
'IntersectionObserver': function() {
return import('intersection-observer');
},
'ResizeObserver': function() {
return import('resize-observer-polyfill')
.then(function(module) {
window.ResizeObserver = module.default;
});
}
};
console.log('Loading polyfill for:', feature);
return polyfillMap[feature] ? polyfillMap[feature]() : Promise.resolve();
},
// Initialize with progressive enhancement
init: function(preferredTier) {
var tier = preferredTier || 'enhanced';
return this.upgradeTier(tier).then(function(achievedTier) {
// Add tier class to document
document.documentElement.classList.add('tier-' + achievedTier);
document.documentElement.setAttribute('data-tier', achievedTier);
// Load tier-specific features
this.loadFeaturesForTier(achievedTier);
return achievedTier;
}.bind(this));
},
// Load features for tier
loadFeaturesForTier: function(tier) {
var features = this.tiers[tier].features;
console.log('Loading features:', features);
if (tier === 'advanced') {
// Enable all advanced features
this.enableLazyLoading();
this.enableAnimations();
this.enableWebComponents();
} else if (tier === 'enhanced') {
// Enable enhanced features
this.enableAsyncData();
this.enableModernUI();
} else {
// Core features only
this.enableBasicUI();
}
},
enableLazyLoading: function() {
console.log('Lazy loading enabled');
// Use IntersectionObserver for lazy loading
},
enableAnimations: function() {
console.log('Animations enabled');
// Use Web Animations API
},
enableWebComponents: function() {
console.log('Web Components enabled');
// Load custom elements
},
enableAsyncData: function() {
console.log('Async data enabled');
// Use fetch for data loading
},
enableModernUI: function() {
console.log('Modern UI enabled');
// Enable modern UI features
},
enableBasicUI: function() {
console.log('Basic UI enabled');
// Minimal UI enhancements
}
};
// Usage
ProgressiveEnhancement.init('enhanced').then(function(tier) {
console.log('App initialized with tier:', tier);
// Start application
initApp(tier);
});
Note: Progressive enhancement ensures everyone gets a working
experience. Load polyfills to upgrade capabilities, never as a hard requirement.
5. Graceful Degradation Implementation Patterns
| Pattern | Strategy | Fallback | Example |
|---|---|---|---|
| Feature Detection | Check before use | Alternative implementation | IntersectionObserver → scroll events |
| Try-Catch Wrapper | Catch unsupported features | Simpler alternative | async/await → Promise chains |
| Polyfill + Fallback | Load polyfill, fallback if fails | Native or nothing | fetch → XMLHttpRequest |
| Conditional Loading | Skip feature entirely | Simplified UX | Skip animations on old browsers |
| Message Display | Inform user of limitations | Upgrade prompt | "Update browser for best experience" |
Example: Graceful degradation utilities
// graceful-degradation.js
var GracefulDegradation = {
// Feature availability cache
features: {},
// Check and cache feature availability
supports: function(feature) {
if (this.features.hasOwnProperty(feature)) {
return this.features[feature];
}
var checks = {
'IntersectionObserver': function() {
return 'IntersectionObserver' in window;
},
'ResizeObserver': function() {
return 'ResizeObserver' in window;
},
'fetch': function() {
return 'fetch' in window;
},
'Promise': function() {
return 'Promise' in window;
},
'async-await': function() {
try {
eval('(async () => {})');
return true;
} catch (e) {
return false;
}
},
'webp': function(callback) {
// Async check for WebP support
var img = new Image();
img.onload = function() {
callback(img.width > 0 && img.height > 0);
};
img.onerror = function() {
callback(false);
};
img.src = 'data:image/webp;base64,UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==';
},
'localStorage': function() {
try {
var test = '__test__';
localStorage.setItem(test, test);
localStorage.removeItem(test);
return true;
} catch (e) {
return false;
}
}
};
var result = checks[feature] ? checks[feature]() : false;
this.features[feature] = result;
return result;
},
// Execute with fallback
withFallback: function(primary, fallback) {
try {
return primary();
} catch (e) {
console.warn('Primary function failed, using fallback:', e);
return fallback();
}
},
// Lazy loading with fallback
lazyLoad: function(selector, options) {
if (this.supports('IntersectionObserver')) {
// Use IntersectionObserver
var observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
var img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
}, options);
document.querySelectorAll(selector).forEach(function(img) {
observer.observe(img);
});
} else {
// Fallback to immediate loading
console.log('IntersectionObserver not supported, loading all images');
document.querySelectorAll(selector).forEach(function(img) {
img.src = img.dataset.src;
});
}
},
// Fetch with XMLHttpRequest fallback
fetchWithFallback: function(url, options) {
if (this.supports('fetch')) {
return fetch(url, options).then(function(response) {
return response.json();
});
} else {
// XMLHttpRequest fallback
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open(options && options.method || 'GET', url);
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
try {
resolve(JSON.parse(xhr.responseText));
} catch (e) {
reject(e);
}
} else {
reject(new Error('Request failed: ' + xhr.status));
}
};
xhr.onerror = function() {
reject(new Error('Network error'));
};
xhr.send(options && options.body);
});
}
},
// Storage with fallback
storage: {
get: function(key) {
if (GracefulDegradation.supports('localStorage')) {
return localStorage.getItem(key);
} else {
// Cookie fallback
var nameEQ = key + '=';
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) === 0) {
return c.substring(nameEQ.length, c.length);
}
}
return null;
}
},
set: function(key, value) {
if (GracefulDegradation.supports('localStorage')) {
localStorage.setItem(key, value);
} else {
// Cookie fallback
document.cookie = key + '=' + value + '; path=/';
}
},
remove: function(key) {
if (GracefulDegradation.supports('localStorage')) {
localStorage.removeItem(key);
} else {
// Remove cookie
document.cookie = key + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/';
}
}
},
// Browser upgrade message
showUpgradeMessage: function() {
var oldBrowsers = [
navigator.userAgent.indexOf('MSIE') !== -1,
navigator.userAgent.indexOf('Trident/') !== -1 &&
navigator.userAgent.indexOf('rv:11') !== -1
];
if (oldBrowsers.some(Boolean)) {
var message = document.createElement('div');
message.className = 'browser-upgrade-message';
message.innerHTML =
'<p>You are using an outdated browser. ' +
'Please <a href="https://browsehappy.com/">upgrade your browser</a> ' +
'to improve your experience and security.</p>';
document.body.insertBefore(message, document.body.firstChild);
}
}
};
// Usage examples
// Lazy loading with fallback
GracefulDegradation.lazyLoad('img[data-src]');
// Fetch with fallback
GracefulDegradation.fetchWithFallback('/api/data')
.then(function(data) {
console.log('Data loaded:', data);
})
.catch(function(error) {
console.error('Failed to load data:', error);
});
// Storage with fallback
GracefulDegradation.storage.set('user', 'John');
console.log('User:', GracefulDegradation.storage.get('user'));
// Show upgrade message for IE
GracefulDegradation.showUpgradeMessage();
Key Takeaways - Legacy Browser Support
- IE11: Requires ~80-100 KB of polyfills, consider dropping or minimal UX
- Safari: Often lags 1-2 years, focus on Observer APIs and Promise methods
- Mobile: Android 4.4 still prevalent in developing markets, needs full polyfills
- Progressive Enhancement: Build core experience first, enhance with polyfills
- Graceful Degradation: Always have fallbacks, never break for old browsers