移动端H5开发-常用技巧篇
引言
笔者是一名前端开发工程师,大部分开发工作都是在 H5 的世界里遨游。多年的 H5 开发经验总结了一些常用技巧,在这里分享给大家,希望在遇到同类问题时能给你提供思路和灵感,保护日益稀疏的头发。
HTML 相关
meta 标签
meta 元素可提供有关页面的元信息。下面是一些与移动端 H5 相关比较特殊的 meta 标签
<meta name="screen-orientation" content="portrait" /> // 禁止屏幕旋转
<meta name="full-screen" content="yes" /> // 全屏显示
<meta name="browsermode" content="application" /> // UC应用模式,页面默认全屏
<meta name="x5-orientation" content="portrait" /> // QQ强制竖屏
<meta name="x5-fullscreen" content="true" /> // QQ强制全屏
<meta name="x5-page-mode" content="app" /> // QQ应用模式
<meta name="renderer" content="webkit" /> // 启用360浏览器的极速模式
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> // 优先使用 IE 最新版本和 Chrome
语义化标签
HTML5 语义化标签能够让页面的结构更清晰,有利于 SEO,并且能够帮助辅助技术更好的阅读和转译你的网页。推荐使用语义化标签编写 HTML
<header></header> // 定义文档的头部区域
<nav></nav> // 定义导航链接的部分
<main></main> // 定义文档的主体部分
<section></section> // 定义文档中的节
<footer></footer> // 定义页面的页脚
<aside></aside> // 定义页面的侧边栏内容
<article></article> // 定义一个文章区域
电话号码拨号与识别
使用 a 标签实现点击手机号码唤起拨号页面
<a href="tel:4009161060" class="phone">400-916-1060</a>
在 iOS Safari 浏览器上会将类似为电话号码的数字识别为电话链接,比如:
7 位数字,形如:1234567 带括号及加号的数字,形如:(+86)123456789 双连接线的数字,形如:00-00-00111 11 位数字,形如:13800138000
关闭识别
<meta name="format-detection" content="telephone=no" />
邮箱识别
使用 a 标签实现点击邮箱地址弹出邮件发送的功能
<a href="mailto:8888@qq.com" class="email">8888@qq.com</a>
Android 手机上会对符合邮箱格式的字符串进行识别,我们可以通过如下的 meta 来关闭邮箱的自动识别
<meta name="format-detection" content="email=no" />
CSS 相关
1px 边框
css 中的 1px 并不等于移动设备的 1px,是因为不同的手机有不同的像素密度。在 window 对象中有一个 devicePixelRatio 属性,他可以反应 css 中的像素与设备的像素比
devicePixelRatio 的官方的定义为:设备物理像素和设备独立像素的比例
完美实现 1px 的方法如下,兼容 DPR(设备像素比) 1-4 的设备
@line-color: #ccc;
[retina] {
position: relative;
}
[retina]::before {
position: absolute;
box-sizing: border-box;
width: 100%;
height: 100%;
font-size: 20px;
content: ' ';
pointer-events: none;
}
/* 设备像素比为 1 */
@media only screen and (-webkit-min-device-pixel-ratio: 1), only screen and (min-device-pixel-ratio: 1) {
[retina]::before {
width: 100%;
height: 100%;
transform: scale(1);
transform-origin: 0 0;
}
[retina='line-bottom']::before {
height: 1px;
}
[retina='line-top']::before {
height: 1px;
}
[retina='line-left']::before {
width: 1px;
}
[retina='rect-input']::before {
border-width: 1px;
}
}
/* 设备像素比为 1.5 */
@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-device-pixel-ratio: 1.5) {
[retina]::before {
width: 150%;
height: 150%;
font-size: 30px;
transform: scale(0.666667);
transform-origin: 0 0;
}
[retina='line-bottom']::before {
height: 1px;
}
[retina='line-top']::before {
height: 1px;
}
[retina='line-left']::before {
width: 1px;
}
[retina='rect-input']::before {
border-width: 1px;
}
[retina='rect-input']::before {
border-width: 1px;
}
}
/* 设备像素比为 2 */
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2) {
[retina]::before {
width: 200%;
height: 200%;
font-size: 40px;
transform: scale(0.5);
transform-origin: 0 0;
}
[retina='line-bottom']::before {
height: 1px;
}
[retina='line-top']::before {
height: 1px;
}
[retina='line-left']::before {
width: 1px;
}
[retina='rect-input']::before {
border-width: 1px;
}
}
/* 设备像素比为 3 */
@media only screen and (-webkit-min-device-pixel-ratio: 3), only screen and (min-device-pixel-ratio: 3) {
[retina]::before {
width: 300%;
height: 300%;
font-size: 60px;
transform: scale(0.333333);
transform-origin: 0 0;
}
[retina='line-bottom']::before {
height: 2px;
}
[retina='line-top']::before {
height: 2px;
}
[retina='line-left']::before {
width: 2px;
}
[retina='rect-input']::before {
border-width: 2px;
}
}
/* 设备像素比为 4 */
@media only screen and (-webkit-min-device-pixel-ratio: 4), only screen and (min-device-pixel-ratio: 4) {
[retina]::before {
width: 400%;
height: 400%;
font-size: 80px;
transform: scale(0.25);
transform-origin: 0 0;
}
[retina='line-bottom']::before {
height: 2px;
}
[retina='line-top']::before {
height: 2px;
}
[retina='line-left']::before {
width: 2px;
}
[retina='rect-input']::before {
border-width: 2px;
}
}
[retina='line-bottom']::before {
bottom: 0;
left: 0;
background-color: @line-color;
transform-origin: 0 100%;
}
[retina='line-top']::before {
top: 0;
left: 0;
background-color: @line-color;
}
[retina='line-left']::before {
top: 0;
left: 0;
background-color: @line-color;
}
[retina='rect-input']::before {
top: 0;
left: 0;
box-sizing: border-box;
border-color: @input-border-color;
border-style: solid;
border-radius: 1em;
}
使用方式很简单
上边框
<div class="container" retina="line-top"></div>
右边框
<div class="container" retina="line-right"></div>
下边框
<div class="container" retina="line-bottom"></div>
左边框
<div class="container" retina="line-left"></div>
全边框
<div class="container" retina="rect-input"></div>
绘制三角形
css 绘制三角形
.triangle-up {
display: inline-block;
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-bottom: 20px solid #409eff;
}
.triangle-right {
display: inline-block;
width: 0;
height: 0;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-left: 20px solid #409eff;
}
.triangle-down {
display: inline-block;
width: 0;
height: 0;
border-right: 10px solid transparent;
border-left: 10px solid transparent;
border-top: 20px solid #409eff;
}
.triangle-left {
display: inline-block;
width: 0;
height: 0;
border-right: 20px solid #409eff;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
}
滚动区域边缘的模糊蒙层
左侧:background: linear-gradient(90deg, #fff, #fff, 50%, hsla(0, 0%, 98%, 0));
右侧:background: linear-gradient(270deg, #fff, #fff, 20%, hsla(0, 0%, 98%, 0));
可换行的文本下划线
<span>“比赛期间作品许可授权声明”的签署短信,签署后完成参赛报名</span>
span {
position: relative;
background: linear-gradient(to bottom, transparent 50%, #FEDC00 50%);
}
音乐唱片旋转与暂停保持动画状态
常常有这样的需求,H5 页面顶部有个播放音乐的图标,点击播放音乐,图标开始旋转,再次点击暂停播放,且图标保持旋转的弧度
@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.music-icon {
animation: rotation 5s linear infinite;
}
.music-icon.paused {
animation-play-state: paused;
}
如上代码,点击暂停时加上样式类 paused,点击播放时去掉样式类 paused
镜像翻转效果
倾斜-1 并且旋转 180 度
transform: scaleX(-1) rotate(180deg);
12px 以下的字体大小实现
大部分桌面浏览器对于小于 12px 的字体会直接展示为 12px,移动端浏览器不会有这样的问题。为了保持两端同步,可以采用缩放来实现
如果设计稿上字体大小是 10px,css 可以写成 20px,然后缩放 0.5 倍大小。缩放后字体位置可能会有偏移,可以使用 translate 来调整
<div class="10pxfont-wrapper">
<div class="10pxfont">我是一段文本</div>
</div>
.10pxfont-wrapper {
display: flex;
justify-content: center;
align-items: center;
width: 375px;
height : 20px;
}
.10pxfont {
width: 200%;
height: 200%;
font-size: 20px;
transform: scale(0.5);
}
屏蔽用户选择
禁止用户选择页面中的文本
.container {
user-select: none;
}
清除输入框内阴影
在 safari 浏览器中,输入框默认有内部阴影,可以这样关闭:
input {
-webkit-appearance: none;
}
禁止保存或拷贝图像
img {
-webkit-touch-callout: none;
}
输入框 placeholder 样式设置
设置 input 里面 placeholder 字体的颜色
input::-webkit-input-placeholder,
textarea::-webkit-input-placeholder {
color: #c2c2c2;
}
用户设置字号放大或者缩小导致页面布局错误
设置字体禁止缩放
body {
text-size-adjust: 100% !important;
}
android 系统中元素被点击时产生边框
部分 android 系统点击一个链接,会出现一个边框或者半透明灰色遮罩, 不同生产商定义出来额效果不一样。去除代码如下
a, button, input, textarea{
-webkit-tap-highlight-color: rgba(0,0,0,0);
-webkit-user-modify:read-write-plaintext-only;
}
iOS 滑动不流畅
ios 手机上下滑动页面会产生卡顿,手指离开页面,页面立即停止运动。整体表现就是滑动不流畅,没有滑动惯性。 iOS 5.0 以及之后的版本,滑动有定义有两个值 auto 和 touch,默认值为 auto
在滚动容器上增加滚动 touch 方法
.scroll {
-webkit-overflow-scrolling: touch;
}
safari 浏览器会给 disabled 的输入框加上 0.4 的不透明度
input:disabled {
opacity: 0.4;
}
不想透明度降低可以这样设置
input:disabled {
color: xxx;
opacity: 1;
-webkit-text-fill-color: xxx;
}
动画实现
loading 动画
接口调用时展示加载 loading
<div class="loading"></div>
.loading {
width: 20px;
height: 20px;
background-image: url(xxxxx);
background-repeat: no-repeat;
animation: rotation 1s infinite;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(1turn);
}
}
逐帧动画之音浪实现方法
播放音频时的音浪如何实现?
<div class="sound-wave">
<div class="icon"></div>
</div>
.sound-wave {
width: 21px;
height: 21px;
}
.sound-wave .icon {
position: relative;
width: 42px;
height: 42px;
background-image: url(../img/sound_wave.png);
background-size: 1008px 42px;
animation: sound_run 1.2s steps(24,start) infinite;
zoom: .5;
}
@keyframes sound_run {
0% {
background-position: 0 0;
}
100% {
background-position: -1008px 0;
}
}
一起来看流星雨
H5 活动类页面实现流星雨效果?
<div class="container">
<div class="flowglow"></div>
</div>
.container {
width: 100vw;
height: 100vh;
}
.container .flowglow {
position: absolute;
top: 0;
right: 0;
width: 22vw;
height: 22vw;
animation: flowglow 5s infinite;
background-image: url(../img/flowglow.png);
background-repeat: no-repeat;
background-position: 0 0;
background-size: contain;
transform: translate3d(22vw, -22vw, 0);
}
@keyframes flowglow {
0% {
transform: translate3d(-22vw, -22vw, 0);
}
100% {
transform: translate3d(-120vw, 76vw, 0);
}
}
页面切换动画,区分前进和后退
在单页应用中,点击链接进入新页面希望页面从右往左滑入,回退到上个页面希望页面从左往右滑出。
以 vue 应用为例,这里会将最外层容器 app-wrapper 绝对定位,宽高 100%,子路由页面高度 100%,内容自适应滚动。这样设计利于页面切换动画的实现。
App.vue
<template>
<div class="app-wrapper">
<transition :name="transitionName">
<keep-alive>
<router-view :key="$route.name"></router-view>
</keep-alive>
</transition>
</div>
</template>
<script lang="ts">
...
transitionName = '';
// 监听路由添加页面切换效果
@Watch('$route')
onRouteChanged(to: Route, from: Route) {
const toDeep = to.meta.deep;
const fromDeep = from.meta.deep;
// 初次进入首页不启用滑动效果
if (from.name === null) {
this.transitionName = '';
} else {
this.transitionName = toDeep === fromDeep ? '' : toDeep < fromDeep ? 'slide-right' : 'slide-left';
}
}
...
</script>
<style lang="less" scoped>
.app-wrapper {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
子页面根样式 {
overflow: atuo;
height: 100%;
}
// transition样式类
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
.slide-right-leave-active,
.slide-top-enter-active,
.slide-top-leave-active,
.slide-bottom-enter-active,
.slide-bottom-leave-active {
position: absolute;
width: 100%;
transition: transform 300ms linear;
}
.slide-left-enter,
.slide-right-leave-to {
transform: translate3d(100%, 0, 0);
}
.slide-top-enter,
.slide-bottom-leave-to {
transform: translate3d(0, 100%, 0);
}
.slide-left-enter-to,
.slide-left-leave,
.slide-right-enter-to,
.slide-right-leave,
.slide-top-enter-to,
.slide-top-leave,
.slide-bottom-enter-to,
.slide-bottom-leave {
transform: translate3d(0, 0, 0);
}
.slide-left-leave-to,
.slide-right-enter {
transform: translate3d(-100%, 0, 0);
}
.slide-top-leave-to,
.slide-bottom-enter {
transform: translate3d(0, -100%, 0);
}
</style>
router.ts:根据页面层级深度设置 meta 中的 deep 参数,页面越深,deep 数字越大
import Vue from 'vue';
import Router, { RouteConfig } from 'vue-router';
import { NavTitle } from './extapi';
Vue.use(Router);
const routes: RouteConfig[] = [
{
path: '/',
alias: ['album', '/album', 'home', '/home'],
name: 'album',
component: () => import(/* webpackChunkName: "album" */ './views/Album.vue'),
meta: {
deep: 0,
title: NavTitle.Album,
},
},
{
path: '/login',
alias: ['login', 'loginPop', '/loginPop'],
name: 'login',
component: () => import(/* webpackChunkName: "login" */ './views/Login.vue'),
meta: {
deep: 1,
title: NavTitle.Login,
},
},
...
]
JS 相关
手机号码输入框禁止输入非数字字符
解决方案:监听输入事件,将非数字字符替换为空字符串
<input
v-model.trim="phone"
type="tel"
placeholder="请输入手机号码"
:maxlength="11"
@input="onInputMessage('phone')"
/>
// 解决在微信内IOS粘贴短信验证码,不显示在方框内的问题
onInputMessage(field: string) {
this[field] = this[field].replace(/[^\d]/g, '');
}
移动端 click 事件会有 300ms 的延时
移动端 click 事件会有 300ms 的延时,原因是移动端屏幕双击会缩放(double tap to zoom) 页面,浏览器需要延时来判断用户操作是点击还是双击事件。解决方案:fastclick
var attachFastClick = require('fastclick');
attachFastClick(document.body);
safari 浏览器日期转换出现 NaN 的问题
将日期字符串的格式符号替换成'/'
'yyyy-MM-dd'.replace(/-/g, '/')