我的第一个H5项目(微信活动)

前言

整个项目开发过程中遇到了很多的问题,包括页面样式、页面布局、背景音乐、图片预加载、页面交互(touch和shake)、微信自定义分享等等。下面将会一一阐述。


页面样式与布局

因为需要有统一的背景音乐,所以最终呈现的页面实际上都是一个个的<div>块,根据页面的交互使用jquery操作css属性display: none/block;来控制<div>块的隐藏与显示。
为了简化开发,页面的所有元素都是直接使用设计师裁剪好的图片,即可以100%还原设计师的设计原型,也省去了页面样式的配置。

  • 将页面中所有会发生交互的元素剥离后,剩余部分直接作为页面的背景图片。
  • 针对会发生交互的元素,以一个button为例,并使用css去配置button的样式,而是把设计师裁剪好的整个button图片直接放到页面上。

背景图片的更换

1
$("body").attr('background','../images/ML.gif');

背景图片自适应屏幕

1
2
3
4
5
6
body{
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed;
background-size:cover;
}

可交互元素(仍然是图片)的样式与布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 图片的大小及水平布局 -->
<div id="kissMiddle" class="col-sm-12" style="padding-left: 18%">
<img id="kissImg" src="../images/亲.gif" alt="" class="img-rounded" width="80%" style="float: left">
</div>
<!-- 图片的垂直布局,自适应屏幕高度 -->
<script>
$(document).ready(function () {
document.getElementById("kissMiddle").style.paddingTop = document.documentElement.clientHeight*0.28+"px";
</script>
<!-- 图片堆叠 -->
#id_{
position: absolute;
top: 0;
left:0;
z-index: 0;
}


背景音乐

html标签

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 旋转动画的图片 -->
<div>
<div class="musicImg">
<img id="rotate" class="rotate" src="../images/music.png" onclick="playPause()" width="100%"/>
</div>
</div>
<!-- audio标签 -->
<div id="music" style="display: none">
<audio id="audioplay" loop="loop" autoplay="autoplay" controls="controls">
<source src="../audio/music1.mp3" />
</audio>
</div>

旋转动画(css)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* 音乐旋转图片 */
.musicImg{
position: absolute;
top: 30px;
right: 30px;
width:70px;
float: right;
z-index: 1
}
/* 音乐旋转动画 */
.rotate {
-webkit-animation: rotating 1.2s linear infinite;
-moz-animation: rotating 1.2s linear infinite;
-o-animation: rotating 1.2s linear infinite;
animation: rotating 1.2s linear infinite;
}

@-webkit-keyframes rotating {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

播放及暂停(js)

1
2
3
4
5
6
7
8
9
10
11
12
function playPause(){
var audio = document.getElementById('audioplay');
//console.log("1" + audio.played);
//console.log("2" + audio.paused);
if(audio.paused){
audio.play();
$("#rotate").attr("class","rotate");
}else{
audio.pause();
$("#rotate").attr("class","rotate_stop");
}
}

图片预加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!-- 图片预加载 -->
<div style="display: none">
<script>
if (document.images) {
var imgML = new Image();
imgML.src = "../images/ML.gif";
$("body").attr('background','../images/ML.gif');

var img1 = new Image();
var img2 = new Image();
var img3 = new Image();
var img4 = new Image();
var img5 = new Image();
var img6 = new Image();
var img7 = new Image();
img1.src = "../images/1.jpg";
img2.src = "../images/2.jpg";
img3.src = "../images/3.jpg";
img4.src = "../images/4.jpg";
img5.src = "../images/5.jpg";
img6.src = "../images/6.jpg";
img7.src = "../images/重新扫描.png";
}
</script>
</div>

页面交互(touch和shake)

多点触控,touch

1
2
3
4
5
6
7
8
9
10
11
12
var kiss = document.querySelector("#kissImg");
var fingers;
kiss.addEventListener("touchstart", function (e){
fingers = e.touches.length;
});
kiss.addEventListener("touchend", function (e){
if(fingers>=2){
//满足2点触控,往下执行
}else{
//少于2点,往下执行
}
});

向上滑动,touch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var b = document.getElementsByTagName("body")[0];
b.addEventListener("touchmove", function(e) {
e.preventDefault();
});
var startY,endY;
var el = document.querySelector("#index");
//获取点击开始的坐标
el.addEventListener("touchstart", function (e){
startY = e.touches[0].screenY;
});
//获取点击结束后的坐标
el.addEventListener("touchend", function(e){
endY = e.changedTouches[0].screenY;
if((startY - endY)>30){
//满足移动距离大于30px,往下执行
}
});

摇一摇,shake

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 引用第三方包 -->
<script src="../js/shake.js"></script>
<script>
var myShakeEvent = new Shake({
threshold: 10
//摇动幅度
});
myShakeEvent.start();
window.addEventListener('shake', shakeEventDidOccur, false);

function shakeEventDidOccur () {
//可以用if增加条件指定div块内触发摇一摇后才往下执行
//摇一摇触发后,往下执行
}
</script>

微信自定义分享

中控服务器计算签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
//Node.js实现,以下为主体代码
function getToken(callback) {
var getTokenUrl = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appId=';
getTokenUrl = getTokenUrl + appId + '&secret=' + appSecret;
request.get(getTokenUrl, function(error, response, body) {
if (error) {
callback('1_getToken error', error);
}
else {
try {
var token = JSON.parse(body).access_token;
callback(null, token);
} catch (e) {
callback('2_getToken error', e);
}
}
});
}

function getTicket(callback) {
var nowTime = Math.floor(new Date().getTime() / 1000);
//先判定ticket是否过期,如果抓取不到数据,设定为过期
try{
var ticketStore = JSON.parse(fs.readFileSync('./js_sdk_ticket.json'));
} catch(e){
var ticketStore = {ticket:'',time: 0};
}
//ticket有效时间是7200s,这里放宽一点
if(ticketStore.time + 7000 > nowTime){
console.log("读取json文件中的ticket成功")
callback(null,ticketStore.ticket)
}else{
getToken(function(resultType,token){
if(resultType != null){
console.log('getNewTicket获取token错误: ' + resultType);
callback('getNewTicket获取token错误',resultType);
}else{
console.log("getNewTicket获取token成功: " + token);
var getTicketUrl = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=';
getTicketUrl = getTicketUrl + token + '&type=jsapi';
request.get(getTicketUrl, function(error, res, body) {
if (error) {
callback('getNewTicket error', error);
}
else {
try {
var ticket = JSON.parse(body).ticket;
fs.writeFileSync('./js_sdk_ticket.json',JSON.stringify({ticket:ticket,time:nowTime}));
callback(null, ticket);
} catch (e) {
callback('getNewTicket error', e);
}
}
});
}
});
}
}

function getSigmature(config,url,callback){
getTicket(function(resultType,ticket){
if(resultType != null){
console.log('getSigmature获取ticket错误: ' + resultType2);
callback('getSigmature获取ticket错误:',resultType2);
}else{
console.log("getSigmature获取ticket成功: " + ticket);
var str = 'jsapi_ticket=' + ticket + '&noncestr=' + config.nonceStr + '&timestamp=' + config.timestamp + '&url=' + url;
config.signature = crypto.createHash("sha1").update(str,'utf-8').digest("hex");
config.ticket = ticket;
callback(null,config)
}
});

}

router.get("/",function(req,res,next){
var jsConfigInfo = {
timestamp: Math.floor(new Date().getTime() / 1000),
nonceStr:Math.random().toString(36).substr(2, 15),
ticket:"",
signature:""
};
var signUrl = req.query.signUrl;
getSigmature(jsConfigInfo,signUrl,function(resultType,config){
res.send(config);
});
});

请求中控服务器获取签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//Node.js实现
function getConfigInfo(url,callback) {
request.get("请求中控服务器的API接口地址?signUrl=" + url, function (error, res, body) {
if (!error) {
console.log(body);
callback(body)
}
})
}

router.get('/index',function(req,res,next){
var url = 'http://xxx.xxx.com' + req.originalUrl;
url = encodeURIComponent(url);
getConfigInfo(url,function(body){
if(body != null){
console.log(body);
body = JSON.parse(body);
res.locals.timestamp = + body.timestamp;
res.locals.nonceStr = body.nonceStr;
res.locals.signature = body.signature;
}
res.render('redLip/index',{title:'XXX'});
});
});

设置自定义分享的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function customShare() {
wx.onMenuShareTimeline({
title: CustomTitle, // 分享标题
link: CustomLink, // 分享链接
imgUrl: CustomimgUrl, // 分享图标
success: function () {
// 用户确认分享后执行的回调函数
},
cancel: function () {
// 用户取消分享后执行的回调函数
}
});
wx.onMenuShareAppMessage({
title: CustomTitle, // 分享标题
desc: 'xxxxxxx', // 分享描述
link: CustomLink, // 分享链接
imgUrl: CustomimgUrl, // 分享图标
type: 'link', // 分享类型,music、video或link,不填默认为link
dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
success: function () {
// 用户确认分享后执行的回调函数
},
cancel: function () {
// 用户取消分享后执行的回调函数
}
});
}

wx.ready(function(){customShare();});

That’s all.
Happy writing!