Last active
January 30, 2024 15:09
-
-
Save fuweichin/f7b675c7a5bab86dd1488962e646c6cc to your computer and use it in GitHub Desktop.
Power Saving Mode detection
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<title>Power Saving Mode detection</title> | |
</head> | |
<body> | |
<div>Power Saving Mode? <code id="powerSavingMode"></code></div> | |
<script type="module"> | |
import {detectPowerSavingMode} from './power-saving.js'; | |
document.addEventListener('DOMContentLoaded', () => { | |
let span = document.querySelector('#powerSavingMode'); | |
span.textContent = ''; | |
if(self !== top && window.safari){ | |
alert('The detection may not work as expected in <iframe> for Safari'); | |
} | |
let requestIdleCallback = window.requestIdleCallback || queueMicrotask; | |
requestIdleCallback(()=>{ | |
detectPowerSavingMode().then((result) => { | |
span.textContent = '' + result; | |
}); | |
}); | |
}); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* detect iOS/iPad/macOS Low Power Mode, Chromium Energy Saver Mode, and maybe future Firefox power saving mode | |
* @async | |
* @method detectPowerSavingMode | |
* @returns {boolean|undefined} return `undefined` if not sure | |
*/ | |
export function detectPowerSavingMode() { | |
// for iOS/iPadOS Safari, and maybe MacBook macOS Safari (not tested) | |
if (/(iP(?:hone|ad|od)|Mac OS X)/.test(navigator.userAgent)) { | |
// In Low Power Mode, cumulative delay effect happens on setInterval() | |
return new Promise((resolve) => { | |
let fps = 60; | |
let interval = 1000 / fps; | |
let numFrames = 30; | |
let startTime = performance.now(); | |
let i = 0; | |
let handle = setInterval(() => { | |
if (i < numFrames) { | |
i++; | |
return; | |
} | |
clearInterval(handle); | |
let actualInterval = (performance.now() - startTime) / numFrames; | |
let ratio = actualInterval / interval; // 1.3x or more in Low Power Mode, 1.1x otherwise | |
// alert(actualInterval+' '+interval); | |
console.log(actualInterval, interval, ratio); | |
resolve(ratio > 1.3); | |
}, interval); | |
}); | |
} | |
// for Safari, Chromium, and maybe future Firefox | |
return detectFrameRate().then((frameRate) => { | |
// In Battery Saver Mode frameRate will be about 30fps or 20fps, | |
// otherwise frameRate will be closed to monitor refresh rate (typically 60Hz) | |
if (frameRate < 34) { | |
return true; | |
} | |
// FIXME fallback to regard as Low Power Mode when battery power is low (down to 20%) | |
else if (navigator.getBattery) { | |
return navigator.getBattery().then((battery) => { | |
return (!battery.charging && battery.level <= 0.2) ? true : false; | |
}); | |
} | |
return undefined; | |
}); | |
} | |
export function detectFrameRate() { | |
return new Promise((resolve) => { | |
let numFrames = 30; | |
let startTime = performance.now(); | |
let i = 0; | |
let tick = () => { | |
if (i < numFrames) { | |
i++; | |
requestAnimationFrame(tick); | |
return; | |
} | |
let frameRate = numFrames / ((performance.now() - startTime) / 1000); | |
resolve(frameRate); | |
}; | |
requestAnimationFrame(() => { | |
tick(); | |
}); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment