线上地址: http://news.qq.com/zt2016/jb/jb.htm
这期的项目做的时间比较长,因为遇见的问题也比较多。
1. 页面动画 #
这里使用了比较多的 CSS3 动画,不过其中有几个不是很成功。
1.1 长按屏幕,背景图片进行渐变 #
这是这个项目里最重要的动画之一,在用户进入到这个页面后,首先执行的就是这个动画,而且着重表现了一个人的受伤过程。即从未受伤的状态渐变成受伤的状态:
html:
<img src="http://mat1.gtimg.com/news/skeetershi/jiabao/img/injure_before.jpg" alt="受伤前" class="bg injure_before" />
<img src="http://mat1.gtimg.com/news/skeetershi/jiabao/img/injure_after.jpg" alt="受伤后" class="bg injure_after" />
javascript:
$('.injure_before').addClass('fadeOut'); // 渐出
$('.injure_before').addClass('fadeIn'); // 渐显
1.2 长按屏幕,手指移动 #
当时写的时候本来是没有这个动画的,后来考虑到 1.1 里的背景图渐变是长按屏幕才能触发,在这长按的过程中,感觉页面像是死掉的样式,于是就在手指这儿添加了一个动画,让手指从线的左端滑动到右端,到右端时触发的动画。
zepto 里的长按longTap
触发时间是 700ms,那我设置手指移动的滑动时间是750ms
,保证动画是连起来的:
默认样式:
position: absolute;
top: -0.08rem;
left: 0;
-webkit-transition: all 750ms linear;
transition: all 750ms linear;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
width: 0.48rem;
结束的样式:
-webkit-transform: translate3d(1.9rem, 0, 0);
transform: translate3d(1.9rem, 0, 0);
1.3 打开档案 #
这个动画在这项目里是比较复杂了,过程说这样的
- 点击按钮后,按钮和捆绳消失
- 档案口进行 180 度的反转(打开)
- 档案向下移动至页面的底部
- 档案内的纸张(div 模拟)滑出
曾经在实现的过程中,2 和 4 产生了冲突,在步骤 2 中,使用的是 CSS3 里的rotate
进行反转,但是若使用了transform
,那么就会导致z-index
失效,档案内的纸张就会盖不住档案口。因此最后的解决方案是:步骤 2 不使用 CSS3 动画,而是直接进行显示(显示打开的状态)和隐藏(隐藏关闭的状态),后面的动画依次执行。
2. 遇见过的问题 #
上面的档案打开动画其实只是兼容问题,并不是所有的手机上都会失效,但是还是要让所有的手机都正常,只能把那个动画取消了,不过最近总结的时候好像有解决办法了。
2.1 刚进入页面或刷新时 #
当用户刚进入页面时,或已进入页面但刷新了,首先出现的不是 loading 页,而是后面的页面,因为我在 css 里把 loading 的层级设置的最高,感觉应该没什么问题。现在是改成,页面初始时,只有 loading 是可见的,其他的 section 只有在 loading 完成之后可见。
2.2 在新闻客户端进行左右滑动 #
新闻客户端有其自己的左右滑动事件,向左滑进入评论(如果有评论),从评论向右滑回到文章,从文章再向右滑则滑出文章进入列表。在安卓
下,我们的左滑和右滑是不管用的;在iPhone
下,可以使用我们自定义的左右滑动事件。因此在最后的上线前,把点击切换和滑动切换都添加上了,安卓下使用点击切换,iPhone 下点击和滑动都可以。
同时,新闻客户端提供了一个可以屏蔽右滑事件(即屏蔽从文章滑出到列表页);在新闻客户端里打开 html5 时,经常会看不到这个页面的标题,只显示默认标题“腾讯新闻”,因此需要在 js 中进行延迟设置:
(function ($) {
$.getScript('http://mat1.gtimg.com/www/js/newsapp/jsapi/news.js?_tsid=1', function () {
var isStopSwipe = false,
ckTencentNews = null;
(function stopSwipe() {
var ua = window.navigator.userAgent.toLowerCase(),
iPhone = /iphone/i.test(ua) ? true : false,
android = /android/i.test(ua) ? true : false;
ckTencentNews = setInterval(function () {
if (window.TencentNews && window.TencentNews.setGestureQuit) {
window.TencentNews.setGestureQuit(true);
if (isStopSwipe) {
clearInterval(ckTencentNews);
}
} else {
clearInterval(ckTencentNews);
}
}, 20);
})();
setTimeout(function () {
document.title = '看见淤青有多难_腾讯新闻_腾讯网';
// set news app title
if (window.TencentNews && window.TencentNews.setTitle) {
window.TencentNews.setTitle(document.title);
}
}, 80);
});
// forbidden weixin swipe
document.addEventListener(
'touchmove',
function (e) {
e.preventDefault();
},
false,
);
})($);
2.3 视频播放 #
视频播放采用的是tvp,虽然设置了自动播放,但是在 iPhone 下还需要再点击一下才能播放。之前不知道这个设置,就在播放器的外城弄了一个遮罩,遮罩上是自定义的播放按钮,一直播放不了;于是修改为:每次点击都重新创建播放器,关闭时删除当前播放器。
同时,在小米品牌的手机下,播放器会悬浮在刚开始播放的位置,且一直固定,不跟随页面滚动。因此,页面中所有的视频都在一个当前居中的 div 中进行播放,该 div 有一个可关闭按钮。
createPlayer : function( $item ){
var $ss = $item.find('.ss'), // 当前的播放区域
vid = $item.attr('vid'); // 播放影片的vid
var video = new tvp.VideoInfo();
video.setVid(vid);
var player = new tvp.Player();
$('.popup_video').show();
player.create({
width: '100%',
height: '100%',
video: video,
playerType: 'html5',
isHtml5UseUI:false,
autoplay: true,
modId: 'pop_mod_player',
onallended:function(){
}
});
// 关闭播放器
$('.popup_video .popup_close').on('tap', function(){
$('#pop_mod_player').html( '' ); // 播放器清空
$('.popup_video').hide(); // 隐藏弹窗
$item.find('.over').show(); // 显示默认图片和播放按钮
})
}
2.4 滚动 #
之所以 iScroll 会诞生,主要是因为无论是在 iphone、ipod、android 或是更早前的移动 webkit 都没有提供一种原生的方式来支持在一个固定高度的容器内滚动内容。 这个不幸的规则导致所有 web-app 要模拟成 app 的样子时,只能由一个绝对定位的 header 或是 footer 再加上一个可以内容的滚动的中间区域组成。
幸运的是移动 webkit 提供了一种强大的硬件加速的 CSS 属性,这个属性可以用来模拟这个缺失的功能,iScroll 从这里开始了前进之路,但是没有不带刺的玫瑰。让内容滚动像原生方式一般比想象中要难。
iScroll 官方 github: iScroll_github
iScroll 中文翻译: iScroll 5 API 中文版
这里需要注意两个问题:图片加载和内容可见。若当前需要初始化的区域有图片,则需要等待图片全部加载完成之后才能初始化,否则高度会有问题;同时当前区域必须是可见的,不能是隐藏状态,否则初始化完成的高度也会有问题,要么是 0,要么仅仅是 client 的高度。
在这里基本上所有的功能都能找到,我在本项目里用到的特殊也不是很多,也只有两个:
- 这个滚动区域和上方的人物信息,上下进行切换,因此这里需要判断,何时进行当前内容的滚动,何时滚动到上方的人物信息区域
- 导航定位
关于第 1 个,我这里的判断,如果当前的滚动条上移且滚动开始位置距离顶部的距离小于等于 14 时,表示需要切换到另一个区域:
myScroll.on('scrollStart', function () {
// this.directionY 1:向下滚动 -1:向上滚动
// this.y 表示文档顶部与容器顶部的位移,该值永远<=0
// console.log( this.y, this.directionY );
if (this.y > -14 && this.directionY == -1) {
$('.people').removeClass('fadeOutUp').addClass('fadeInDown');
// $('.story').removeClass('fadeInUp').addClass('fadeOutDown');
}
});
导航定位,即通过导航定位到内容的某个位置,iScroll 里提供了两个方法: scrollTo(滚动到某个位置) 和 scrollBy(滚动多远),在导航定位中应该使用scrollTo
。但是在使用这个方法之前,首先得获取每个位置的 offset 值。在获取每个位置的 offset 值之前,首先保证所有图片都加载完成,或者每个图片都是定高的也行。
2.5 背景音乐 #
在人物列表里,大概有 10 个人物,但是只有 4 个人物有背景音乐;而且冲背景音乐切换到长文内容时,背景音乐也要暂停。因此这里的逻辑是这样的:
- 背景视频默认不播放,用户点击按钮时才播放
- 当前人物有背景音乐时,且处于播放状态,则进行播放;若是禁播状态(默认关闭状态或用户主动暂停音乐),即使有背景音乐也不播放
- 当前人物没有背景音乐时,前一个背景音乐暂停
- 当从人物列表切换到长文内容时,背景音乐暂停;再且回来时,依然是暂停状态
javascript:
// 切换背景音乐
var music = $next.attr('music') || '', // 下一个即将展示的人物的背景音乐
$bgMusicContr = $('#bgMusicContr'); // 播放按钮
// 若有背景音乐
if (music !== '') {
$('#bgMusic').prop('src', music);
$bgMusicContr.show();
// 若当前处于播放状态,才进行播放
if ($bgMusicContr.hasClass('playing')) {
bgMusic.play();
}
} else {
$bgMusicContr.hide();
bgMusic.pause();
}
2.6 轮播图的高度 #
轮播图里的每个 item 都是position:absolute
属性,父级元素撑不开,而且,每个图片对应的说明文字的长度也是不固定的,因此得需要计算出每个 item 的高度。
// 设置轮播图的高度
setSliderHeight : function(){
var $slider = $('.slider');
// 对每个轮播图都进行设置
for(var i=0, len=$slider.length; i<len; i++){
var $item = $slider.eq(i),
$img = $item.find('img'),
$p = $item.find('ul p'),
img_height = $img.eq(0).height(), // 每个轮播图里的图片都是定高的,因此只取第一个item的图片的高度
p_mar_top = parseInt($p.eq(0).css('margin-top')),
p_mar_btm = parseInt($p.eq(0).css('margin-bottom')),
p_height = $p.eq(0).height(),
p_all = p_mar_top+p_height+p_mar_btm,
p_max = p_all;
// 计算出说明里文字最多的
for(var j=1, p_len=$p.length; j<p_len; j++){
var pp_mar_top = parseInt($p.eq(j).css('margin-top')),
pp_mar_btm = parseInt($p.eq(j).css('margin-bottom')),
pp_height = $p.eq(j).height(),
pp_all = pp_mar_top+pp_height+pp_mar_btm;
if( pp_all > p_max ){
p_max = pp_all;
}
}
var $slide_control = $item.find('.slide_control');
if( $slide_control.length ){
var slide_control_btm = parseInt($slide_control.css('bottom'));
$slide_control.css('bottom', p_max+slide_control_btm);
}
$item.css( 'height', img_height+p_max ).children('ul').css('height', img_height);
}
}
2.7 给 slider 添加切换按钮 #
在长文内容页里,关于图片,有单张图和轮播图两种方式,这两种方式的样式是相同的,唯一的区别就是:轮播图可以左右切换,同时图片的下方有指示当前图片的圆点。2.6 和 2.7 是对相同轮播图进行设置的,只是功能不一样而已。因此
// 给slider添加切换按钮
addSliderDot: function() {
var $slider = $('.slider'),
lent = $slider.length;
if (lent > 1) {
for (var j = 0; j < lent; j++) {
var $item = $slider.eq(j),
$li = $item.find('li'),
len = $li.length,
html = '';
// 当图片的数量大于1时,才显示切换按钮
if (len > 1) {
html = '<img src="http://mat1.gtimg.com/news/skeetershi/jiabao/img/arrow_left.png" alt="" class="arrow arrow_left">' +
'<img src="http://mat1.gtimg.com/news/skeetershi/jiabao/img/arrow_right.png" alt="" class="arrow arrow_right">' +
'<div class="slide_control">';
for (var i = 0; i < len; i++) {
var s = i === 0 ? 'mall_dot mall_dot_current' : 'mall_dot';
html += '<a href="javascript:;" class="' + s + '">' + (i + 1) + '</a>';
}
html += '</div>';
}
$item.append(html);
}
}
}
2.8 在新闻客户端和微信中设置分享信息 #
这个项目会在新闻客户端和微信中同时推送,因此需要让用户在这两个客户端都能分享给朋友和分享到朋友圈。
信息配置:
// 信息配置
var shareData = {
title: '反家暴',
img: 'http://mat1.gtimg.com/news/skeetershi/jiabao/img/share.jpg',
desc: '伤害,是身体上的淤青,也是赤膊爱情的荆棘。',
link: document.location.href,
};
分享代码:
// 分享代码,复制进项目里即可
function newsappShare() {
var timer = null;
timer = setTimeout(function () {
if (window.TencentNews) {
if (window.TencentNews && window.TencentNews.setShareArticleInfo) {
window.TencentNews.setShareArticleInfo(
shareData.title,
shareData.title,
shareData.desc,
shareData.link,
shareData.img,
);
} else {
window.TencentNews.shareFromWebView(
shareData.title,
shareData.title,
title.desc,
shareData.link,
shareData.img,
);
}
if (window.TencentNews && window.TencentNews.setGestureQuit) {
window.TencentNews.setGestureQuit(true) || window.TencentNews.setGestureQuit(1);
}
}
}, 300);
}
window.addEventListener(
'load',
function () {
newsappShare();
},
false,
);
function onBridgeReady() {
WeixinJSBridge.on('menu:share:timeline', function (e) {
var data = {
img_width: '120',
img_height: '120',
img_url: shareData.img,
link: shareData.link,
desc: shareData.desc,
title: shareData.title,
};
WeixinJSBridge.invoke('shareTimeline', data, function (res) {
WeixinJSBridge.log(res.err_msg);
});
});
WeixinJSBridge.on('menu:share:weibo', function () {
WeixinJSBridge.invoke(
'shareWeibo',
{
content: shareData.desc,
url: shareData.link,
},
function (res) {
WeixinJSBridge.log(res.err_msg);
},
);
});
WeixinJSBridge.on('menu:share:appmessage', function (argv) {
WeixinJSBridge.invoke(
'sendAppMessage',
{
img_width: '120',
img_height: '120',
img_url: shareData.img,
link: shareData.link,
desc: shareData.desc,
title: shareData.title,
},
function (res) {
WeixinJSBridge.log(res.err_msg);
},
);
});
}
//执行
document.addEventListener('WeixinJSBridgeReady', function () {
onBridgeReady();
});
3. 总结 #
这个项目做的时间比较长,也加了不少的班,真是遇到了不少的问题。不好好总结一一下都对不起自己。