- 2016年的老文,搬运存档用 -
企鹅 FM 点歌台,UI 侧大概是踩到了3个坑
企鹅 fm 点歌台是 2.5 将会新出的功能,其中有两个点觉得值得分享,一个是老生常谈轮子都不知道造了多少个的 banner 轮播,还有一个就是企鹅 FM 移动端对齐的弹幕。
在实践过程中,我们尝试了2种方式,无论哪个方法,结构都是视口>轮播容器>banner容器+banner容器...。
原理图看起来比较抽象,要结合一下 HTML 结构来说,会比较好:
.slider-wrapper
.slider(style="/*transform: translateX(-375px);*/")
.banner(style="left:0%;")
.banner(style="left:100%;")
.banner(style="left:200%;")
.banner(style="left:300%;")
.banner(style="left:-100%;")
.banner(style="left:-200%;")
.banner(style="left:-300%;")
轮播器的初始状态如上,slide-wrapper
指的是视口,视口的宽度是固定的:
.slider-wrapper{
overflow: hidden;
position: relative;
z-index: 0;
width: 100%;
}
滚动的时候,要有动画效果:
.slider{
will-change: -webkit-transform;
-webkit-transition: -webkit-transform 0.4s cubic-bezier(0, 0, 0.2, 1);
-o-transition: -webkit-transform 0.4s cubic-bezier(0, 0, 0.2, 1);
transition: -webkit-transform 0.4s cubic-bezier(0, 0, 0.2, 1);
}
高度是通过内容来撑的:
.slider .banner{
position: absolute;
top: 0;
bottom: 0;
z-index: 0;
width: 100%;
background-position: bottom;
background-size: cover;
}
.slider .banner:after{
content: "";
display: block;
padding-top: 40%;
}
.slider
每次移动一屏幕宽的距离,因为 .banner
的 left
是用百分比来写的,也是一屏幕一屏幕地移动。这里一共有7个 banner 需要轮播,在 .slider
移动之后,还出现在视口(.slider-wrapper
)左边的 banner, 每个 left
都加上 700%,就会按照现有的相对顺序跑到最右边。
这个方法的缺点就是,.slider
的 translateX
的值会越来越大,.slider
的 left
也会越来越大。目前是没有发现什么性能上的问题,但是不知道会不会遇到 stackoverflow 之类的问题。当一直停留在这个页面上,不断进行计算,我怀疑会不会出现数位不够,无法继续往下计算的状况。
.slider-wrapper
还是需要内联样式:width: 屏幕宽度
setInterval
或者 setTimeout
或者 RAF,移动 ```.slider
screen.width()
在安卓上会得到实际像素(比如魅族MX4,就会得到1080px),如果要使用这个方法,获取屏幕宽度是不可以的,可以获取视口宽度li.banner
是用绝对定位写的,在移动的过程中 left
的值还在改变,所以在计算 translate
的时候,在部分安卓机上 webview 会有问题,轮播不会通过流畅的动画切换,而是轮播区域黑一下,再闪现下一张解决上面两个问题,就可以了。
如上提到,使用绝对定位写轮播,会出现因为计算引起的 bug。为了避免这个问题,所以我们回到了最传统的写无限轮播的方法:
.slider-wrapper
.slider
.banner(style="background-image:url(img/banner_01.jpg);")
.banner(style="background-image:url(img/banner_02.jpg);")
.banner(style="background-image:url(img/banner_03.jpg);")
.banner(style="background-image:url(img/banner_04.jpg);")
.banner(style="background-image:url(img/banner_05.jpg);")
.banner(style="background-image:url(img/banner_06.jpg);")
.banner(style="background-image:url(img/banner_01.jpg);")
将每一个 .banner
一行排开,是用 flexbox 实现的,.slider-wrapper
和 .slider
的宽度都是屏幕宽度:
.slider-wrapper{
overflow: hidden;
position: relative;
z-index: 0;
width: 100%;
}
.slider{
will-change: -webkit-transform;
-webkit-transition: -webkit-transform 0.4s cubic-bezier(0, 0, 0.2, 1);
-o-transition: -webkit-transform 0.4s cubic-bezier(0, 0, 0.2, 1);
transition: -webkit-transform 0.4s cubic-bezier(0, 0, 0.2, 1);
width: 100%;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
}
.slider .banner{
width: 100%;
background-position: bottom;
background-size: cover;
width: 100%;
-webkit-flex-shrink: 0;
-ms-flex: 0 0 auto;
flex-shrink: 0;
}
.slider .banner:after{
content: "";
display: block;
padding-top: 40%;
}
当按照顺序播放到结构的最后一张,也就是上面的最后一个 .banner
结构,背景是 banner_01.jpg
时,通过 JS 迅速切换到第一个 .banner
,它们两个的背景都是 baner_01.jpg
,切换成功以后,轮播部分的 translate
都被更新了,可以开始新的一轮播放了。
要看看 JS 没有加载好之前,你的页面是什么样的?Chrome Dev Tools > 按下 F1 > 勾选 Disable JavaScript。当页面需要 JS 参与进来做一些计算或者一些调整(比如轮播)的时候,开发者可以看到当 JS 来不及加载好之前,用户看到的是什么的,保证了在网络糟糕、JS 堵塞或者 JS 被禁用的情况下,我们做出来的页面是不是还能看。
任何事情开始之前都是困难的,一旦开始了,就完成了一半。在做项目的时候,我常常有这样的体会。从弹幕需求到真正实现,这句话又出现在我的脑海中。现在要写总结了,它又冒了出来。如上文说到的,点歌台项目中,将会用 CSS 模拟企鹅 FM 客户端的弹幕效果
从某种程度上说,弹幕的实现和轮播有异曲同工之妙,也是视口+滚动区域的模式。
原谅我这个野生的美工的示意图。
红色区域是视口,黑色的矩形长条是评论,白色区域是滚动区域即评论容器。HTML 结构如下:
.cmt-wrapper
.cmt-list
.cmt-item
.cmt-item
.cmt-item
.cmt-item
.cmt-item
.cmt-item
...
.cmt-wrapper
即视口,只有滚动到视口区域中的弹幕,才会被展现出来:
.cmt-wrapper{
position: absolute;
bottom: 3.75rem;
left: 55px;
z-index: 20;
overflow-y: hidden;
padding-left: 1px;
height: 215px;
width: 248px;
}
一开始呢,用户是看不到弹幕的,弹幕是从下往上出现的,所以动区域要藏在视口的底下:
.cmt-list{
position: absolute;
top: 215px;
left: 0;
z-index: 0;
-webkit-transition: -webkit-transform 500ms;
-o-transition: -webkit-transform 500ms;
transition: -webkit-transform 500ms;
}
所以滚动区域的 top
值就是视口区域的高度。滚动区域每一次向上移动多少呢?即将显示的 .cmt-item
高度(弹幕可能是一行也可能是两行,所以移动的高度无法固定)。
弹幕容器 .cmt-item
本身的样式很普通,有一点要注意的,每一条弹幕的初始状态不应该在动画类中写,而应该一开始就写好。每条弹幕的动画是以各自左下角为中心,缩小到0,因为之后每条弹幕的显示是通过 setInterval
来控制的,红米在计算时间和渲染上有某种 bug,会出现某几条弹幕动画来不及执行:
.aod-share .cmt-item{
margin-bottom: 15px;
position: relative;
-webkit-transform-origin: 0 100%;
-moz-transform-origin: 0 100%;
-ms-transform-origin: 0 100%;
-o-transform-origin: 0 100%;
transform-origin: 0 100%;
-webkit-transform: scale(0);
-ms-transform: scale(0);
-o-transform: scale(0);
transform: scale(0);
}
但,弹幕的出现和消失是有动效的:
不过还有最后一个问题:要在什么时候让哪一个 .cmt-item
消失呢?
因为滚动区域是从下到上滚动,而视口是保持在同一位置,以下是初始状态:
当再滚动一下(要注意滚动的幅度哦),滚动区域和视口会出现接壤或者滚动区域会跑到视口的上面了,那么第一个 .cmt-item
就要加上 .anim-hide
了:
最后的效果大概是这样的:
安卓和 iOS 键盘呼起时页面的形态不同,iOS 上会将页面上移一点,保证输入区域不会被键盘挡住,此时键盘是盖在页面上的。而安卓上会将整个页面上移,键盘和页面会形成有接壤但不重合的两个区域:
iOS 处理的很智能,所以一般不用担心它。但是安卓上就不一样了,整个页面都网上顶了,普通文档流还好,不会出现遮挡的状况,但是用 position:absolute
定位的页面就不太好,像点歌台里面的这个页面,DOM 结构可以简化为:
.wrapper
textarea
样式则是:
.wrapper{
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.wrapper textarea{
position: absolute;
bottom: 1rem;
left: 50%;
transform: translateX(-50%);
}
这样写,如果 textarea
稍微高一点,输入区域一定会出现被遮挡或部分遮挡的状况。所以现在的解决办法是:不用 bottom
,用 top
。也就是 textarea
相关区域用 top
来定位。不过万一出现键盘很高,占了屏幕的 2/3,所剩区域本来就不多,偏偏 top
值又定的很大,输入区域直接掉出了页面…那也是没sei了。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。