Files
login_task_web/src/utils/mobile.js

329 lines
7.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 移动端交互优化工具
*/
// 检测移动设备
export const isMobileDevice = () => {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
}
// 检测触摸设备
export const isTouchDevice = () => {
return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0
}
// 检测设备方向
export const getOrientation = () => {
return window.innerWidth > window.innerHeight ? 'landscape' : 'portrait'
}
// 监听方向变化
export const onOrientationChange = (callback) => {
let orientation = getOrientation()
const handleResize = () => {
const newOrientation = getOrientation()
if (newOrientation !== orientation) {
orientation = newOrientation
callback(newOrientation)
}
}
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}
// 防止iOS缩放
export const preventIOSZoom = () => {
let lastTouchEnd = 0
const preventZoom = (e) => {
const now = Date.now()
if (now - lastTouchEnd <= 300) {
e.preventDefault()
}
lastTouchEnd = now
}
document.addEventListener('touchend', preventZoom, { passive: false })
return () => {
document.removeEventListener('touchend', preventZoom)
}
}
// 添加触摸反馈
export const addTouchFeedback = (element, options = {}) => {
const {
className = 'touch-feedback',
duration = 150,
scale = 0.98
} = options
if (!element) return
const style = document.createElement('style')
style.textContent = `
.${className} {
transform: scale(${scale});
transition: transform ${duration}ms ease;
}
`
document.head.appendChild(style)
const addFeedback = () => {
element.classList.add(className)
setTimeout(() => {
element.classList.remove(className)
}, duration)
}
element.addEventListener('touchstart', addFeedback, { passive: true })
return () => {
element.removeEventListener('touchstart', addFeedback)
if (style.parentNode) {
style.parentNode.removeChild(style)
}
}
}
// 滚动到顶部
export const scrollToTop = (smooth = true) => {
window.scrollTo({
top: 0,
behavior: smooth ? 'smooth' : 'auto'
})
}
// 滚动到元素
export const scrollToElement = (element, options = {}) => {
const {
behavior = 'smooth',
block = 'start',
inline = 'nearest',
offset = 0
} = options
if (!element) return
const elementTop = element.offsetTop + offset
window.scrollTo({
top: elementTop,
behavior
})
}
// 获取安全区域
export const getSafeArea = () => {
const style = getComputedStyle(document.documentElement)
return {
top: parseInt(style.getPropertyValue('env(safe-area-inset-top)')) || 0,
right: parseInt(style.getPropertyValue('env(safe-area-inset-right)')) || 0,
bottom: parseInt(style.getPropertyValue('env(safe-area-inset-bottom)')) || 0,
left: parseInt(style.getPropertyValue('env(safe-area-inset-left)')) || 0
}
}
// 处理键盘弹起
export const handleVirtualKeyboard = () => {
let initialViewportHeight = window.innerHeight
const handleResize = () => {
const currentHeight = window.innerHeight
const heightDifference = initialViewportHeight - currentHeight
// 键盘弹起时的处理高度差超过150px认为是键盘弹起
if (heightDifference > 150) {
document.body.classList.add('keyboard-open')
document.body.style.height = `${currentHeight}px`
} else {
document.body.classList.remove('keyboard-open')
document.body.style.height = ''
}
}
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
document.body.classList.remove('keyboard-open')
document.body.style.height = ''
}
}
// 防抖函数
export const debounce = (func, wait) => {
let timeout
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout)
func(...args)
}
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}
// 节流函数
export const throttle = (func, limit) => {
let inThrottle
return function(...args) {
if (!inThrottle) {
func.apply(this, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
// 复制到剪贴板
export const copyToClipboard = async (text) => {
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text)
return true
} else {
// 降级方案
const textArea = document.createElement('textarea')
textArea.value = text
textArea.style.position = 'fixed'
textArea.style.left = '-999999px'
textArea.style.top = '-999999px'
document.body.appendChild(textArea)
textArea.focus()
textArea.select()
try {
document.execCommand('copy')
return true
} catch (err) {
console.error('复制失败:', err)
return false
} finally {
textArea.remove()
}
}
} catch (err) {
console.error('复制失败:', err)
return false
}
}
// 震动反馈
export const vibrate = (pattern = 50) => {
if ('vibrate' in navigator) {
navigator.vibrate(pattern)
}
}
// 全屏显示
export const requestFullscreen = (element = document.documentElement) => {
if (element.requestFullscreen) {
return element.requestFullscreen()
} else if (element.webkitRequestFullscreen) {
return element.webkitRequestFullscreen()
} else if (element.msRequestFullscreen) {
return element.msRequestFullscreen()
}
}
// 退出全屏
export const exitFullscreen = () => {
if (document.exitFullscreen) {
return document.exitFullscreen()
} else if (document.webkitExitFullscreen) {
return document.webkitExitFullscreen()
} else if (document.msExitFullscreen) {
return document.msExitFullscreen()
}
}
// 检测网络状态
export const getNetworkStatus = () => {
if ('connection' in navigator) {
const connection = navigator.connection
return {
online: navigator.onLine,
effectiveType: connection.effectiveType,
downlink: connection.downlink,
rtt: connection.rtt,
saveData: connection.saveData
}
}
return {
online: navigator.onLine
}
}
// 监听网络变化
export const onNetworkChange = (callback) => {
const handleOnline = () => callback({ online: true })
const handleOffline = () => callback({ online: false })
window.addEventListener('online', handleOnline)
window.addEventListener('offline', handleOffline)
return () => {
window.removeEventListener('online', handleOnline)
window.removeEventListener('offline', handleOffline)
}
}
// PWA安装提示
export const handlePWAInstall = () => {
let deferredPrompt
const beforeInstallPrompt = (e) => {
e.preventDefault()
deferredPrompt = e
}
const showInstallPrompt = async () => {
if (deferredPrompt) {
deferredPrompt.prompt()
const { outcome } = await deferredPrompt.userChoice
deferredPrompt = null
return outcome === 'accepted'
}
return false
}
window.addEventListener('beforeinstallprompt', beforeInstallPrompt)
return {
showInstallPrompt,
cleanup: () => {
window.removeEventListener('beforeinstallprompt', beforeInstallPrompt)
}
}
}
export default {
isMobileDevice,
isTouchDevice,
getOrientation,
onOrientationChange,
preventIOSZoom,
addTouchFeedback,
scrollToTop,
scrollToElement,
getSafeArea,
handleVirtualKeyboard,
debounce,
throttle,
copyToClipboard,
vibrate,
requestFullscreen,
exitFullscreen,
getNetworkStatus,
onNetworkChange,
handlePWAInstall
}