Common Compatibility Issues
Below is a summary of common compatibility issues and their solutions.
Safari does not support requestIdleCallback by default
It's only not supported by default; users can manually enable it. (But we cannot ask users to do that, so we must avoid using this API.)
iOS Safari Triggering Downloads
When iOS Safari downloads files that can be opened in the browser, such as PDF, it prefers to open them inline rather than triggering a download.
const iosDownload = url => {
fetch(url)
.then(res => res.blob())
.then(blobRes => {
// wrapping in an extra Blob to force download instead of preview
const blob = new Blob([blobRes]);
const url = URL.createObjectURL(blob);
const aEl = document.createElement('a');
aEl.href = url;
aEl.download = `${title}.pdf`;
aEl.target = '_blank';
document.body.appendChild(aEl);
aEl.click();
document.body.removeChild(aEl);
URL.revokeObjectURL(url);
hideTextTip();
});
};
tailwind uses the is selector by default
tailwind build output contains the is selector, which requires a relatively high browser engine version. This can cause missing styles on older devices.
Solution: install postcss-pseudo-is
Fixed Bottom Button Positioning and Keyboard Pop-up
Fixing a button to the bottom of the screen is a common requirement, usually achieved with position: fixed;.
However, on some Android devices, the virtual keyboard reduces the window height, causing the fixed bottom button to move up, leaving only the button in the already small window.
The solution is to listen for the resize event and change the button's positioning when the window shrinks or expands.
// button positioning = buttonSticky ? 'sticky' : 'static'
let buttonSticky = false;
// record window height first
const winHeight = document.documentElement.clientHeight;
window.addEventListener("resize", function () {
// window height decreased
if (document.documentElement.clientHeight < winHeight) {
console.log("Detected window shrank, possibly keyboard popped up");
buttonSticky = false;
} else {
console.log("Window enlarged, possibly keyboard closed");
buttonSticky = true;
}
});
WeChat Browser and Image Saving
In WeChat browser, downloading base64 images via a tag has compatibility issues; it may fail to download properly.
In WeChat browser, image downloads are usually implemented by long-pressing to save the image.
Clipboard Copy Compatibility Code
Clipboard API has two requirements for the execution environment:
Browser engine version (Chrome 66+)
Secure context
Thus the following compatibility code is used:
const setClipboard = (text: string, cb?: () => void) => {
navigator.clipboard
.writeText(text)
.then(() => {
cb?.();
})
.catch((err) => {
console.error("Async: Could not copy text: ", err);
//for compatibility
const input = document.createElement("input");
input.value = text;
input.style.position = "fixed";
input.style.opacity = "0";
document.body.appendChild(input);
input.select();
document.execCommand("copy");
document.body.removeChild(input);
cb?.();
});
};
export default setClipboard;
Hiding Scrollbar in H5
The scrollbar on PC can be hidden as follows:
-webkit-scrollbar {
width: 0;
height: 0;
}
But on mobile, it doesn't work. On mobile, it should be hidden like this:
However,
Safariseems unable to completely hide the scrollbar no matter what:
-webkit-scrollbar {
display: none;
}
If the scrollbar needs to be displayed, you can use the unset keyword.
Safari GET Request Drops Parameters – Pitfall
The queryString parameter should not contain a non-conforming "-". Non-conforming parameters will be simply ignored by Safari.
Chrome handles such non-conforming queryString specially, but Safari simply discards those parameters.
Safari Missing Separator Lines
Apple devices (iOS/Mac) occasionally fail to render very thin separator lines.
There are two cases:
1. External monitor with low resolution.
1. Enabled `px2rem` for screen adaptation, and `post-css` compiled `1px` borders into a very small `rem` value.
The first case is hard to solve except by making the border thicker. It's also strange: the line displays normally on the Mac built-in screen but loses detail on an external monitor. Dragging the window between screens can cause the border to appear and disappear. This may be related to Mac's graphics rendering logic.
For the second case, you can configure post-css to not compile the pixel value of border.
Safari Input Issues
Minimal reproduction demo:
import { Input } from "@mui/material";
import { useEffect } from "react";
export default function MyTest() {
useEffect(() => {
document.documentElement.style.fontSize = "4px";
}, []);
return (
<>
raw : <input type="text" className="w-[25rem]" />
mui : <Input className="w-[25rem]"></Input>;
</>
);
}
When opening this page in iPhone Safari, the cursor of the input box will be offset to the left.
This may be because the root element's font size is smaller than 12px; the smaller it is, the more noticeable the bug. So the solution is to avoid setting the html font too small.
theme-color
Safari supports theme-color, but Chrome seems not to.
For example, when browsing Youku in Safari, the entire window turns black.
Downloading PDF in WeChat Browser
The requirement is to download PDF in WeChat browser.
"Download" here means saving to the device locally, not opening automatically.
Android
If you download a blob via js and then trigger download with an a tag, Android will prompt: 'Please open the browser to download'. However, in the browser, the address bar shows a blob URL, so the download cannot complete.
Solution:
window.open(url, "_blank");
iOS
If you directly use window.open on iOS, it will open a preview of the PDF instead of downloading it.
The solution is to convert to blob via js to trigger the download.
Solution:
fetch(url)
.then((res) => res.blob())
.then((blobRes) => {
// const blob = new Blob([blobRes]);
// Uncommenting this line can trigger download on ios_safari ???
const blob = blobRes;
const url = URL.createObjectURL(blob);
const aEl = document.createElement("a");
aEl.href = url;
aEl.download = `${title}.pdf`;
aEl.target = "_blank";
document.body.appendChild(aEl);
aEl.click();
document.body.removeChild(aEl);
URL.revokeObjectURL(url);
});