百度百科给出的定义:
微信小程序,英文名Wechat Mini Program,是一种不需要下载安装即可使用的应用,它实现了应用“触手可及”的梦想,用户扫一扫或搜一下即可打开应用。
然后看下我未认证的小程序效果:
主页:
博客系列:
整体效果:
2010年腾讯正筹划一项大事,那就是做一款比QQ更纯粹,更高级的交流软件——微信。
2011年1月21日,微信1.0版本正式上线,那时候大家还停留在QQ的世界里,对微信也并不敏感。
2011年5月10日,微信发布2.0版本,推出了一项语音功能,微信也算迈出了自己的第一步;
2011年8月,微信发布2.5版本,新增“查看附近的人”交友功能,此时用户突破1500万;
2011年10月,微信发布3.0版本,新增“摇一摇”和“漂流瓶”功能,这段时间也是微信用户高速增长的阶段;
2011年12月,微信发布3.5 版本,也带来了全新的二维码“扫一扫”交友功能;
2012年3月,微信用户数突破1亿;4月,微信发布4.0版本,新增了“朋友圈相册”功能,并确定英文名称为“Wechat”;
2012年7月,微信发布4.2版本,增加视频聊天功能,同时发布网页版微信;
2012年8月23日,微信公众平台上线,微信开始构建内容生态;
2013年1月15日深夜,腾讯微信团队宣布:微信用户数突破3亿;
2013年7月,微信国内注册用户突破4亿,8月5日, 微信支付正式上线;
2013年10月24日,微信的注册用户突破6亿,每日活跃用户1亿;
2014年1月,微信红包在春节前夕正式上线;
2014年年3月,开放微信支付功能,并开放微信支付接口;
2016年1月11日,微信之父张小龙时隔多年的公开亮相,解读了微信的四大价值观。张小龙指出,越来越多产品通过公众号来做,因为这里开发、获取用户和传播成本更低。拆分出来的服务号并没有提供更好的服务,所以微信内部正在研究新的形态,叫「微信小程序」;
2016年3月1日,微信支付对转账功能停止收取手续费。同时,对提现功能开始收取手续费;
2016年8月,微信获香港首批支付牌照;
2016年9月21日,微信小程序正式开启内测;
2017年1月9日,张小龙宣布小程序正式上线;微信小程序的存在让我们既省了手机容量,又省了下载流量,为我们带来了巨大的便利;
2017年12月,微信发布6.6.1 版本,开放了小游戏,还重点推荐了小游戏「跳一跳];
2018年2月,微信全球用户月活数首次突破10亿;
2019年11月,微信新增“腾讯QQ”小程序;
2020年6月17日,微信上线“拍一拍”功能;
直到今天,微信触及了我们生活的方方面面,似乎大家的生活已经和微信绑在一起了。
而小程序是一种新的开放能力,开发者可以快速地开发一个小程序。小程序可以在微信内被便捷地获取和传播,同时具有出色的使用体验。
官网给我们的列举的步骤:
1)注册
在微信公众平台注册小程序,完成注册后可以同步进行信息完善和开发。官网注册说明
2)小程序信息完善
填写小程序基本信息,包括名称、头像、介绍及服务范围等。
3)开发小程序
完成小程序开发者绑定、开发信息配置后,开发者可下载开发者工具、参考开发文档进行小程序的开发和调试。
下载开发者工具,如果是win7,最高版:wechat_devtools_1.05的版本。
下载好微信开发者工具:
然后创建小程序:
AppID:注册成功后,我们可以在小程序后台,找到AppID;
后端服务:微信云开发是腾讯云为移动开发者提供的一站式后端云服务,弱化后端和运维概念,让开发者可以专注于业务逻辑的实现,无需搭建服务器,使用平台提供的 API (云函数、云数据库、云存储)进行业务开发即可,云开发中提供的少部分模板是免费的;而不使用云开发,也就是传统的开发,需要自己搭建环境,毕竟上线后需要定期进行数据维护等工作。
模板选择:可以选择使用Typescript和JavaScript等语言来开发;
注:Typescript是微软开发的一个开源的编程语言,通过在JavaScript的基础上添加静态类型定义构建而成,最终可以转译成JavaScript代码。
新建好一个项目:
4)提交审核和发布
完成小程序开发后,提交代码至微信团队审核,审核通过后即可发布(公测期间不能发布)。
2.2、目录结构
官网的描述:小程序包含一个描述整体程序的
app
和多个描述各自页面的page
。
1)一个小程序主体部分由三个文件组成,必须放在项目的根目录
文件 | 必需 | 作用 |
---|---|---|
app.js | 是 | 小程序逻辑(管理整个程序的生命周期) |
app.json | 是 | 小程序公共配置(包括了小程序的所有页面路径、界面表现、网络超时时间、底部 tab 等) |
app.wxss | 否 | 小程序公共样式表(全局CSS样式) |
2)一个小程序页面由四个文件组成
文件类型 | 必需 | 作用 |
---|---|---|
js | 是 | 页面逻辑 |
wxml | 是 | 页面结构 |
json | 否 | 页面配置,json是一种数据格式 |
wxss | 否 | 页面样式表 |
3)其他文件
文件类型 | 必需 | 作用 |
---|---|---|
project.config.json | 是 | 开发者工具的配置文件 |
project.private.config.json | 是 | 项目私有配置文件 |
sitemap.json | 否 | 配置小程序及其页面是否允许被微信索引 |
微信小程序采用的原生框架叫做MINA,关于小程序更详细的教程可以看官方教程,这里只挑选主要内容。
app.js包括了小程序的所有页面路径、界面表现、网络超时时间、底部 tab 等。小程序默认的配置:
{
"pages":[
"pages/index/index",
"pages/logs/logs"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "Weixin",
"navigationBarTextStyle":"black"
},
"style": "v2",
"sitemapLocation": "sitemap.json"
}
用于指定小程序由哪些页面组成,每一项都对应一个页面的 路径(含文件名) 信息。文件名不需要写文件后缀,框架会自动去寻找对应位置的 .json
, .js
, .wxml
, .wxss
四个文件进行处理。
未指定 entryPagePath
时,数组的第一项代表小程序的初始页面(首页)。
我们使用微信开发者工具直接在pages中新增一个page页面,工具自动帮我们生成了一个目录及相应的页面文件。(如果是使用的VSCODE开发工具的话是不会帮我们新建的)
指定小程序的默认启动路径(首页),常见情景是从微信聊天列表页下拉启动、小程序列表启动等。如果不填,将默认为 pages
列表的第一项。
在app.json配置文件中新增entryPagePath字段,并设置启动路径:
{
"pages":[
"pages/blog/blog",
"pages/index/index",
"pages/logs/logs"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "Weixin",
"navigationBarTextStyle":"black"
},
"style": "v2",
"sitemapLocation": "sitemap.json",
"entryPagePath":"pages/index/index"
}
案例效果:页面多了个主页图标
定义小程序所有页面的顶部背景颜色,文字颜色定义等。
默认的window就是我们前面看到的Weixin头部信息。官网window字段文档
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "Weixin",
"navigationBarTextStyle":"black"
}
接下来我们修改点app.json里的内容:
{
"pages":[
"pages/blog/blog",
"pages/index/index",
"pages/logs/logs"
],
"window":{
"backgroundTextStyle":"dark",
"navigationBarBackgroundColor": "#00bfff",
"navigationBarTitleText": "穆瑾轩的博客",
"navigationBarTextStyle":"white",
"enablePullDownRefresh":true
},
"style": "v2",
"sitemapLocation": "sitemap.json"
}
backgroundTextStyle,下拉 loading 的样式,enablePullDownRefresh开启全局的下拉刷新。
案例效果:
通过上面的学习,我们大致了解到微信小程序的页面布局:
但是有人会想,怎么看有的小程序的标题有其他颜色或者字体比我的大?微信对于window的设置并没有开放字体大小及颜色的设置(固定的样式,页面比较稳定,不会溢出)。但是微信提供了navigationStyle:'custom'来设置自定义的导航,官方对于navigationStyle的说明:
自定义导航需要了解小程序的自定义组件,这个后面再演示。
如果小程序是一个多 tab 应用(客户端窗口的底部或顶部有 tab 栏可以切换页面),可以通过 tabBar 配置项指定 tab 栏的表现,以及 tab 切换时显示的对应页面。
通常用的比较多的是:顶部tabBar和底部的tabBar。
tabBar的一些属性:
1)一级属性
属性 | 类型 | 必填 | 默认值 | 描述 | 最低版本 |
---|---|---|---|---|---|
color | HexColor | 是 | tab 上的文字默认颜色,仅支持十六进制颜色(未选中时) | ||
selectedColor | HexColor | 是 | tab 上的文字选中时的颜色,仅支持十六进制颜色 | ||
backgroundColor | HexColor | 是 | tab 的背景色,仅支持十六进制颜色 | ||
list | Array | 是 | tab 的列表,详见 list 属性说明,最少 2 个、最多 5 个 tab |
||
position | string | 否 | bottom | tabBar 的位置,仅支持 bottom / top |
|
custom | boolean | 否 | false | 自定义 tabBar,见详情 | 2.5.0 |
2)二级属性
属性 | 类型 | 必填 | 说明 |
---|---|---|---|
pagePath | string | 是 | 页面路径,必须在 pages 中先定义 |
text | string | 是 | tab 上按钮文字 |
iconPath | string | 否 | 图片路径,icon 大小限制为 40kb,建议尺寸为 81px * 81px,不支持网络图片。 当 position 为 top 时,不显示 icon。 |
selectedIconPath | string | 否 | 选中时的图片路径,icon 大小限制为 40kb,建议尺寸为 81px * 81px,不支持网络图片。 当 position 为 top 时,不显示 icon。 |
官网中有给我们说明一些属性的意思:
新增tabBar配置:
{
"pages":[
"pages/blog/blog",
"pages/person/person",
"pages/index/index",
"pages/logs/logs"
],
"window":{
"backgroundTextStyle":"dark",
"navigationBarBackgroundColor": "#00bfff",
"navigationBarTitleText": "穆瑾轩的博客",
"navigationBarTextStyle":"white",
"enablePullDownRefresh":true
},
"style": "v2",
"sitemapLocation": "sitemap.json",
"tabBar":{
"list":[
{
"pagePath":"pages/blog/blog",
"text": "博客",
"iconPath":"images/bk3.png",
"selectedIconPath":"images/bk2.png"
},
{
"pagePath":"pages/person/person",
"text": "关于我",
"iconPath":"images/bk_gyw1.png",
"selectedIconPath":"images/bk_gyw2.png"
}
],
"selectedColor":"#FF6F00",
"position":"bottom"
}
}
案例效果:
每一个小程序页面也可以使用同名 .json
文件来对本页面的窗口表现进行配置,页面中配置项会覆盖 app.json
的 window
中相同的配置项。
注:并不是所有的都能覆盖,具体以官网列举的为准。
例如,我配置person.json文件如下:
{
"usingComponents": {},
"enablePullDownRefresh":false,
"navigationBarBackgroundColor":"#00cccc"
}
案例效果:
导航栏的背景颜色被改变,且无法下拉刷新。
在学习页面配置前,我们先了解下小程序给我提供了哪些组件来渲染页面。打开官网的组件页面,我们可以看到有这些组件:
1)视图容器 2)基础内容 3)表单组件 4)导航 5)媒体组件 6)地图 7)画布 8)开放能力 9)原生组件说明 10)无障碍访问 11)导航栏 12)页面属性配置节点
如果之前了解过VUE,对组件的概念应该就不那么陌生了。组件是视图层的基本组成单元,可以扩展页面元素,是对UI层的封装。
这里只挑选几个常用的(视图容器、基础内容、表单组件、导航)演示。
一个容器可以容纳多个组件,并使他们成为一个整体。容器可以简化图形化界面的设计,以整体结构来布局界面。
常用视图容器有view、scroll-view、swiper、swiper-item
3.3.1.1、view组件
view组件是页面中最基本的容器组件。类似于Html中的div,用来进行页面布局的,具有块级盒子特性,能够接受其他组件的嵌入。
view组件可以实现横向布局、纵向布局、嵌套等效果。
属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
---|---|---|---|---|---|
hover-class | string | none | 否 | 指定按下去的样式类。当 hover-class="none" 时,没有点击态效果 |
1.0.0 |
hover-stop-propagation | boolean | false | 否 | 指定是否阻止本节点的祖先节点出现点击态 | 1.5.0 |
hover-start-time | number | 50 | 否 | 按住后多久出现点击态,单位毫秒 | 1.0.0 |
hover-stay-time | number | 400 | 否 | 手指松开后点击态保留时间,单位毫秒 | 1.0.0 |
1)横向布局
person.wxml
flex-direction: row——横向布局
A
B
C
person.wxss
/* pages/person/person.wxss */
.flex-item{
width: 200rpx;
height: 200rpx;
text-align: center;
line-height: 200rpx;
}
.demo-text-1{
background-color: red;
}
.demo-text-2{
background-color: green;
}
.demo-text-3{
background-color: pink;
}
.container .flex-wrp{
display: flex;
flex-direction: row;
}
案例效果:
2)纵向布局
修改显示方式为按列显示
.container .flex-wrp{
display: flex;
flex-direction: column;
}
案例效果:
3)嵌套
person.wxml
view-hover:视图点击生态-不阻止父类
我是父类容器
我是子类容器
view-hover:视图点击生态-阻止父类
我是父类容器
我是子类容器
person.wxss
/* pages/person/person.wxss */
.view-parent {
width: 100%;
height: 350rpx;
background-color: pink;
text-align: center;
}
.view-son {
width: 50%;
height: 200rpx;
background-color: blue;
margin: 20rpx auto;
text-align: center;
line-height: 200rpx;
}
.view-hover {
background-color: green;
}
案例效果:不阻止的点击子容器全部变绿,阻止的点击子容器只有子容器变绿。
3.3.1.2、scroll-view组件
scroll-view是可滚动视图容器组件。当视图无法完全展示内容时,可以采取滑动的方式,使控件显示其完整内容。
scroll-view的属性比较多,详细查看官网介绍。
1)纵向滚动
person.wxml
scroll-view:可滚动视图-纵向滚动
A
B
C
D
person.wxss
.scroll-y-item{
width: 200rpx;
height: 200rpx;
text-align: center;
line-height: 200rpx;
}
.scrolly{
height: 200rpx;
width: 200rpx;
}
.bg_green{
background-color: green;
}
.bg_red{
background-color: red;
}
.bg_blue{
background-color: blue;
}
.bg_yellow{
background-color: yellow;
}
案例效果:
2)横向滚动
person.wxml
scroll-view:可滚动视图-横向滚动
A
B
C
D
person.wxss
/* pages/person/person.wxss */
.scroll-x-item{
width: 200rpx;
height: 200rpx;
display: inline-block;
text-align: center;
line-height: 200rpx;
}
.scrollx{
width: 400rpx;
white-space: nowrap;
border: 1px red solid;
}
.bg_green{
background-color: green;
}
.bg_red{
background-color: red;
}
.bg_blue{
background-color: blue;
}
.bg_yellow{
background-color: yellow;
}
案例效果:
3.3.1.3、swiper和swiper-item组件
swiper是滑块视图容器组件,其中只可放置swiper-item组件,否则会导致未定义的行为。通常用于图片间的切换,也就是所谓的轮播图。
person.wxml
swiper:滑块视图容器-轮播图
A
B
C
D
person.wxss
/* pages/person/person.wxss */
.swiper-item{
width: 100%;
text-align: center;
line-height: 300rpx;
}
.swiper{
height: 300rpx;
}
.bg_green{
background-color: green;
}
.bg_red{
background-color: red;
}
.bg_blue{
background-color: blue;
}
.bg_yellow{
background-color: yellow;
}
案例效果:
其他视图容器组件cover-view,movable-view,page-container等等使用方式看官网。
3.3.2.1、icon组件和progress组件
icon图标组件,这个对于我们来说应该不陌生了,很多系统图标、软件图标就是用的扩展名为*.icon、*.ico格式的图片。
progress是进度条组件。
这个官网上就比较详细了。
3.3.2.1、text组件和rich-text组件
text是文本组建,类似于HTML中的span标签,是一个行内元素。
text 组件内只支持 text 嵌套,text组件和view组件的区别就是除了文本节点以外的其他节点都无法长按选中。
rich-text是富文本组建,支持把HTML字符串渲染成wxml结构。
1)text组件测试
我是view组件
我是text组件,测试超长是否自动换行的哈哈哈哈哈哈哈哈哈
我是text组件,支持长按选中,\n测试手动换行
案例效果:
表单组件有:button、checkbox、form、input、label、picker-view、radio、slider、switch、textarea等等。
组件名称 | 组件说明 |
---|---|
button | 按钮组件 |
checkbox | 复选框组件 |
form | 表单组件 |
input | 输入框组件 |
label | 标签组件 |
picker-view | 滚动选择器组件 |
radio | 单选组件 |
slider | 滑动选择器组件 |
switch | 开关组件 |
textarea | 多行文本框组件 |
3.3.3.1、button组件
button是按钮组件,功能比HTML中的button丰富很多,通过open-type属性可以调用微信提供的各种功能(打开客户会话、转发、获取用户手机号、获取用户信息、打开app、打开授权页面、获取用户头像等)。
案例效果:新版样式
旧版样式:
媒体组件主要是用来处理图片、视频、音频的组件。主要包括:
组件名称 | 组件说明 |
---|---|
camera | 系统相机,如:扫描二维码(微信6.7.3版本) |
image | 图片 |
live-player | 实时音视频播放 |
live-pusher | 实时音视频录制 |
video | 视频 |
viop-room | 多人音频视频对话 |
3.3.4.1、camera组件
camera组件可以调用系统相机,支持拍照、扫码二维码功能。同一页面只能插入一个 camera
组件。
person.wxml
预览
person.js
// pages/person/person.js
Page({
takePhoto() {
const ctx = wx.createCameraContext()
ctx.takePhoto({
quality: 'high',
success: (res) => {
this.setData({
src: res.tempImagePath
})
}
})
},
error(e) {
console.log(e.detail)
}
})
案例效果:
3.3.4.2、image组件
image图片组件,支持 JPG、PNG、SVG、WEBP、GIF 等格式。
image组件默认宽度320px、高度240px;image组件中二维码/小程序码图片不支持长按识别。仅在 wx.previewImage 中支持长按识别。
原图:
使用image组件的mode属性来指定图片裁剪和缩放模式。
image
图片
{{item.text}}
person.js
Page({
data: {
array: [{
mode: 'scaleToFill',
text: 'scaleToFill:不保持纵横比缩放图片,使图片完全适应'
}, {
mode: 'aspectFit',
text: 'aspectFit:保持纵横比缩放图片,使图片的长边能完全显示出来'
}, {
mode: 'aspectFill',
text: 'aspectFill:保持纵横比缩放图片,只保证图片的短边能完全显示出来'
}, {
mode: 'top',
text: 'top:不缩放图片,只显示图片的顶部区域'
}, {
mode: 'bottom',
text: 'bottom:不缩放图片,只显示图片的底部区域'
}, {
mode: 'center',
text: 'center:不缩放图片,只显示图片的中间区域'
}, {
mode: 'left',
text: 'left:不缩放图片,只显示图片的左边区域'
}, {
mode: 'right',
text: 'right:不缩放图片,只显示图片的右边边区域'
}, {
mode: 'top left',
text: 'top left:不缩放图片,只显示图片的左上边区域'
}, {
mode: 'top right',
text: 'top right:不缩放图片,只显示图片的右上边区域'
}, {
mode: 'bottom left',
text: 'bottom left:不缩放图片,只显示图片的左下边区域'
}, {
mode: 'bottom right',
text: 'bottom right:不缩放图片,只显示图片的右下边区域'
}],
src: '/images/image_mm.jpg'
},
imageError: function(e) {
console.log('image3发生error事件,携带值为', e.detail.errMsg)
}
})
案例效果:
其他组件用到时在学习。
前面我们学习了小程序的基础配置和一些常用组件。在开发小程序的页面时,我们会在wxml用一些组件,并且附上一些wxss样式及一些逻辑*.js代码去渲染我们的页面,所以我们经常会去编辑这些文件(*.wxml、*.wxss、*.js)。
小程序中的法语和vue中的语法类似,但是又有一些区别。
WXML(WeiXin Markup Language)是框架设计的一套标签语言,结合基础组件、事件系统,可以构建出页面的结构。
2.3.1.1、WXML与HTML区别
WXML与HTML的标签名不同
HTML(div、span、img、a)
WXML有自己的一套内置的组件(view、text、image、navigator)
2.3.1.2、数据绑定
WXML提供了类似于Vue中的模板语法。在data中定义数据,WXML中使用数据:
例如:
blog.wxml
blog.js
// pages/blog/blog.js
Page({
/**
* 页面的初始数据
*/
data: {
imgPath:"/images/image_mm.jpg"
}
})
案例效果:
2.3.1.3、列表渲染
{{ item.message }}
{{item}}
wx:for
在组件上使用 wx:for
控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件。
默认数组的当前项的下标变量名默认为 index
,数组当前项的变量名默认为 item
使用 wx:for-item
可以修改当前项的默认变量(item
)名称。
使用 wx:for-index
可以指定数组当前下标的变量(index
)名称。
wx:key
优点类似于vue中的:key
的用法。如果列表中项目的位置会动态改变或者有新的项目添加到列表中,并且希望列表中的项目保持自己的特征和状态时需要提供,如不提供 wx:key
,会报一个 warning
, 如果明确知道该列表是静态,或者不必关注其顺序,可以选择忽略。
wx:key
的值以两种形式提供:
a)字符串,数组中 item 的某个 property,要确保项保持自己的特征,此时的property的值是需要唯一的。
b)保留关键字 *this
,代表在 for 循环中的 item 本身,items本身作为一个唯一的字符串或者数字。
例如:
blog.js
Page({
/**
* 页面的初始数据
*/
data: {
array1:[1,2,3,4],
array2:[
{id:"1",username:"a"},
{id:"2",username:"b"},
{id:"3",username:"c"}
]
}
})
案例效果:
Page({
/**
* 页面的初始数据
*/
data: {
array1:[1,2,3,4],
array2:[
{id:"1",username:"a"},
{id:"2",username:"b"},
{id:"3",username:"c"}
]
}
})
2.3.1.4、条件渲染
wx:if
vue中使用v-if和v-show控制元素的显示和隐藏。
小程序中使用 wx:if=""
和hidden控制元素的显示和隐藏。
true
false
block wx:if
view1
view2
并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性。
wx:if和hidden的区别:
一般来说,wx:if
有更高的切换消耗而 hidden
有更高的初始渲染消耗。因此,如果需要频繁切换的情景下,用 hidden
更好,如果在运行时条件不大可能改变则 wx:if
较好。
2.3.1.5、其他
WXML提供模板(template),可以在模板中定义代码片段,然后在不同的地方调用。模板用法,具体参考官网。
WXML 提供两种文件引用方式import
和include
,具体用法,参照官网。
WXSS (WeiXin Style Sheets)是一套样式语言,用于描述 WXML 的组件样式。
2.3.2.1、WXSS和CSS的区别
CSS中需要手动进行像素单位换算;
WXSS在底层支持新的尺寸单位rpx,在不同大小的屏幕上小程序会自动进行换算;
WXSS支持样式导入,使用@import
语句;
WXSS仅支持部分CSS选择器。
详细内容至官网查看。
生命周期是指一个对象从创建-运行-销毁的整个过程。相比Vue的生命周期,小程序的生命周期要简单一点。
小程序中的生命周期分为两类:
1)应用生命周期:小程序从启动-运行-销毁的过程;
2)页面生命周期:每个页面的加载-渲染-销毁的过程;
2.3.3.1、应用生命周期
小程序的应用生命周期在app.js中进行声明。详解内容,看官网
// app.js
App({
onLaunch() {
// 小程序初始化完成时执行,全局只触发一次,做一些初始化工作
console.log("onLaunch");
},
onShow(){
//小程序启动或从后台进入前台显示时触发
console.log("onShow");
},
onHide(){
//小程序从前台进入后台时触发
console.log("onHide");
},
onError(msg){
//小程序发生脚本错误或 API 调用报错时触发
console.log("onHide"+msg);
},
onPageNotFound(res){
//小程序要打开的页面不存在时触发。
wx.redirectTo({
url: 'pages/...'
})
},
onUnhandledRejection(){
//小程序有未处理的 Promise 拒绝时触发
console.log("onUnhandledRejection");
},
onThemeChange(){
//系统切换主题时触发
console.log("onThemeChange");
}
})
2.3.3.2、页面生命周期
小程序的页面生命周期需要在页面的.js文件中进行声明。更详细的看官网。
Page({
onLoad: function(options) {
// 生命周期回调—监听页面加载.
},
onShow: function() {
// 生命周期回调—监听页面显示.
},
onReady: function() {
// 生命周期回调—监听页面初次渲染完成.
},
onHide: function() {
// 生命周期回调—监听页面隐藏.
},
onUnload: function() {
// 生命周期回调—监听页面卸载.
}
...
})
打开小程序:(App)onLaunch --> (App)onShow --> (Pages)onLoad --> (Pages)onShow --> (pages)onRead
进入下一个页面:(Pages)onHide --> (Next)onLoad --> (Next)onShow --> (Next)onReady
离开小程序:(App)onHide
MINA 框架提供丰富的微信原生 API,如获取用户信息,本地存储,支付功能等。api在开发中实践,具体看官网。
首页底部导航栏是比较好实现的,设置navigationBar窗口信息,再添加tabBar,包括:推荐、博客、关于我即可。
{
"pages":[
"pages/index/index",
"pages/blog/blog",
"pages/person/person",
"pages/logs/logs"
],
"window":{
"backgroundTextStyle":"dark",
"navigationBarBackgroundColor": "#00bfff",
"navigationBarTitleText": "穆瑾轩的博客",
"navigationBarTextStyle":"white",
"enablePullDownRefresh":true
},
"style": "v2",
"sitemapLocation": "sitemap.json",
"tabBar":{
"list":[
{
"pagePath":"pages/index/index",
"text": "推荐",
"iconPath":"images/home.png",
"selectedIconPath":"images/homeSelected.png"
},
{
"pagePath":"pages/blog/blog",
"text": "博客",
"iconPath":"images/bk3.png",
"selectedIconPath":"images/bkSelected.png"
},
{
"pagePath":"pages/person/person",
"text": "关于我",
"iconPath":"images/bk_gyw1.png",
"selectedIconPath":"images/bk_gywSelected.png"
}
],
"selectedColor":"#FF6F00",
"position":"bottom"
}
}
案例效果:
首页轮播图的实现比较简单,前面我们在学习swiper组件时候就实现过。
1)index.wxml
2)index.wxss
.page{
background-color: #FFFFFF;
height: 100%;
}
.banner{
width: 750rpx;
height: 375rpx;
white-space: nowrap;
}
3) index.js
// index.js
Page({
data: {
runbo:[
{
id:'1',
imgsrc:'/images/rb_image1.jpg'
},
{
id:'2',
imgsrc:'/images/rb_image2.jpg'
},
{
id:'3',
imgsrc:'/images/rb_image3.jpg'
}
]
}
})
案例效果:
首页推荐内容分两块,一块是最新内容,一块是推荐内容。本来想接入公众号的,无奈非认证的个人小程序没有这个资格。
1)index.wxml
—— 最新 ——
{{articles.title}}
{{articles.content}}
{{articles.createTime}}
—— 推荐 ——
{{articles.title}}
{{articles.content}}
{{articles.createTime}}
2)index.wxss
.page{
background-color: #FFFFFF;
height: 100%;
}
.banner{
width: 750rpx;
height: 375rpx;
white-space: nowrap;
}
.article-follow{
box-sizing: border-box;
padding: 0;
margin: 0;
line-height: 100rpx;
font-size: 32rpx;
text-align: center;
color: #666666;
}
.weui-tabs-bar__wrp {
border-bottom: 1px solid #eee;
margin-top: 10px;
}
.weui-tabs-swiper {
width: 100%;
height: 100px;
}
.tab-content .tab-content-item{
height: 100px;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
padding: 40rpx;
}
.weui-tabs-bar__title {
margin: 0px 10px;
font-size: 14rpx;
}
.tab-bar-title__selected {
font-size: 16px;
font-weight: bold;
}
.article-card{
margin: 5rpx 0rpx 100rpx 0rpx;
background: #ffffff;
border-style: solid;
box-shadow:0px 2px 4px 2px #DDDDDD;
border-width: 0rpx;
}
.article-card .content {
width: 750rpx;
padding-bottom: 5rpx;
}
.article-card .operation {
padding: 10rpx 30rpx 0rpx 30rpx;
}
.article-card .operation .date {
font-size: 26rpx;
color:#666666;
}
.article-card .content .cover {
width: 750rpx;
height: 100%;
}
.article-card .content .title {
font-size: 32rpx;
padding: 10rpx 30rpx 5rpx 30rpx;
display: block;
}
.article-card .content .desc {
font-size: 26rpx;
color:#CCCCCC;
padding: 0rpx 30rpx;
display: block;
}
3)index.js
Page({
data: {
runbo:[
{
id:'1',
imgsrc:'/images/rb_image1.jpg'
},
{
id:'2',
imgsrc:'/images/rb_image2.jpg'
},
{
id:'3',
imgsrc:'/images/rb_image3.jpg'
}
],
zx_articles: [],
tj_articles: [],
},
// 事件处理函数
bindViewTap() {
},
onLoad() {
//设置文章列表
const zx_articles = [
{
id: 1,
imageUrl: '/images/bk_article.jpeg',
title: '重学设计模式-设计模式总结',
content:'设计模式并不是一种具体的技术,它讲述的是解决问题的思想,是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案...',
createTime:'2022-07-02 23:22:59',
contentPath:'/pages/article/article'
}
]
this.setData({ zx_articles });
const tj_articles = [
{
id: 2,
imageUrl: '/images/bk_article_gui.jpeg',
title: 'Java GUI——Java图形用户界面',
content:' 早期,电脑向用户提供的是单调、枯燥、纯字符状态的“命令行界面(CLI)...',
createTime:'2021-10-10 23:22:59',
contentPath:'/pages/article/article'
},
{
id: 3,
imageUrl: '/images/bk_article_vue.png',
title: 'vuepress使用简介及个人博客搭建',
content:' vuepress 是 Vuejs 官方提供的一个是Vue驱动的静态网站生成器,基于Markdown语法生成网页...',
createTime:'2021-08-20 23:22:59',
contentPath:'/pages/article/article'
}
]
this.setData({ tj_articles });
}
})
案例效果:
首页推荐列表完成了,接下来实现以下文章详情页面。由于未认证的小程序的各种限制,导致我无法从我的博客或者公众号中直接获取文章信息。那文章详情页面我们在app.json中再加一个detail页面。然后再新建一个template模板,文章的格式比较固定,不同的文章我们用模板去实现。
1)articles.wxml
{{articlesData.title}}
{{articlesData.author}}
{{articlesData.createTime}}
{{articlesData.commonContent}}
{{contents.text}}
{{contents.text}}
2)detail.wxml
3)detail.js
// pages/detail/detail.js
Page({
/**
* 页面的初始数据
*/
data: {
articlesData:{}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
var that = this;
if(options.id=='1'){
that.setData({
articlesData:{
id:'1',
title:'设计模式总结',
author:'作者:穆瑾轩',
createTime:'2022-07-02 23:22:59',
commonContent:'公众号:java穆瑾轩;原文:CSDN-https://blog.csdn.net/xiaoxianer321',
content:[
{
type:'h3',
text:'1、设计模式总结',
},
{
type:'p',
text:' 至此,我们已经完成了23种设计模式的学习,最后很有必要做个总结。\n \n 设计模式并不是一种具体的技术,它讲述的是解决问题的思想,是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案,是对类的封装性、继承性和多态性,以及类的关联关系和组合关系的充分理解与应用。\n\n 在软件工程中有个概念,叫高内聚低耦合,它是作为评判一个软件设计的好坏标准之一。\n\n 高内聚:内聚针对的是模块的内部设计,是指一个软件模块内的各个元素彼此结合的紧密程度,高内聚就是一个模块内各个元素彼此结合的紧密度高。简单的说就是一个模块是否由相关性很强的代码组成,没有冗余和过于复杂的逻辑,只负责一项任务,也就是常说的符合单一职责原则。\n\n 低耦合:耦合针对的是模块(或类)与模块(或类)之间交互的复杂度,模块间的耦合度取决于模块间接口的复杂性、调用方式以及传递信息,传递的信息越少往往耦合度就越低。高内聚低耦合可以保证服务的独立性以及系统的灵活性。因此我们学习设计模式的核心思想与之不谋而合——解耦合(减少可能增加耦合度的设计)。\n\n 不管是前端、还是后端都在趋向于组件化,甚至是应用架构上也诞生了微服务,而组件化、微服务化的思想都是高内聚低耦合的体现。\n\n 我们学习设计模式的核心思想就是解耦合,并不是消除耦合,而是把耦合控制在一定范围,找到稳定点和变化点,运用抽象,把变化点隔离起来。先满足设计原则,再迭代出设计模式。\n\n 其实各设计模式之间都是有共通之处的(在java中是抽象、继承、多态、组合的综合运用),有些看起来十分类似但又能解决不同的问题,这些都是前人总结的经验,你也可以组合出更适用于你所编写的程序的一种模式。'
}
]
}
})
}else if(options.id=='2'){
var that = this;
that.setData({
articlesData:{
id:'2',
title:'Java GUI——Java图形用户界面',
author:'作者:穆瑾轩',
createTime:'2021-10-10 23:22:59',
commonContent:'公众号:java穆瑾轩;原文:CSDN-https://blog.csdn.net/xiaoxianer321',
content:[
{
type:'h3',
text:'1、Java GUI概述',
},
{
type:'h4',
text:'1.1、GUI的前世今生',
},
{
type:'p',
text:' 早期,电脑向用户提供的是单调、枯燥、纯字符状态的“命令行界面(CLI)”。如:Windows中的DOS窗口。后来,Apple公司率先在电脑的操作系统中实现了图形化的用户界面(Graphical User Interface,简称GUI),但由于Apple公司封闭的市场策略,与其它PC不兼容。这使得Apple公司错过了一次一统全球PC的好机会。后来,Microsoft公司推出了风靡全球的Windows操作系统,它凭借着优秀的图形化用户界面,一举奠定了操作系统标准的地位。\n\n 在这图形用户界面风行于世的今天,一个应用软件没有良好的GUI是无法让用户接受的。而Java语言也深知这一点的重要性,它提供了一套可以轻松构建GUI的工具。\n\n AWT,Java最早的界面库。(java.awt:Abstract Windows ToolKit(抽象窗口工具包),需要调用本地系统方法来实现功能,属重量级控件。)\n\n Swing,是对AWT的扩展。(javax.swing:在AWT的基础上, 建立的一套图像界面系统,其中提供了更多的组件,而且完全由Java实现。增强了移植性,属轻量级组件。)\n\n JavaFX,JDK1.8引入的新的界面库。\n\n SWT,Eclipse使用的界面库。它吸收了AWT和Swing实现的最好的部分,SWT于2001年与Eclipse IDE(Integrated Development Environment)一起集成发布。在这个最初发布版之后,SWT发展和演化为一个独立的版本。 JFace的构建基于SWT,它提供了SWT的功能和更简易的MVC模式。SWT和JFace不仅使Java成为一个构建桌面应用程序的可行的选择,也使之成为一个具有优势的开发平台。'
}
]
}
})
}else if(options.id=='3'){
var that = this;
that.setData({
articlesData:{
id:'3',
title:'vuepress使用简介及个人博客搭建',
author:'作者:穆瑾轩',
createTime:'2021-08-20 23:22:59',
commonContent:'公众号:java穆瑾轩;原文:CSDN-https://blog.csdn.net/xiaoxianer321',
content:[
{
type:'h3',
text:'1、vuepress概述 ',
},
{
type:'p',
text:' vuepress 是 Vuejs 官方提供的一个是Vue驱动的静态网站生成器,基于Markdown语法生成网页。简单的说它就是一个快速建设文档站点的工具,在简单配置好功能后,需要做的事情就剩下写好一个个 Markdown 文档,并且可以将其发布到github。\n'
},
{
type:'h3',
text:'2、vuepress简介 ',
},
{
type:'h4',
text:'2.1、vuepress搭建 ',
},
{
type:'h4',
text:'2.2、vuepress目录结构说明 ',
},
{
type:'h4',
text:'2.3、MarkDown语法简介 ',
}
]
}
})
}
},
})
4)detail.json
{
"navigationBarTitleText":"文章详情"
}
5)detail.wxss
/* pages/detail/detail.wxss */
.article-title{
box-sizing: border-box;
padding: 0;
margin: 0;
word-break: break-all;
overflow: auto;
font-weight: bold;
font-size: 40rpx;
justify-content:center;
display: flex;
}
.article-sub-title{
box-sizing: border-box;
margin: 0;
font-size: 30rpx;
padding: 10rpx 0rpx 10rpx 10rpx;
}
.article-common-content{
box-sizing: border-box;
font-size: 24rpx;
align-items: center;
color: deepskyblue;
padding: 10rpx 0rpx 10rpx 10rpx;
}
.contents-p{
font-size: 30rpx;
margin-left: 4rpx;
padding: 10rpx 0rpx 10rpx 10rpx;
}
.contents-text{
box-sizing: border-box;
min-height: 50rpx;
font-size: 30rpx;
}
.contents-h1{
font-size: 40rpx;
font-weight: bold;
padding: 10rpx 0rpx 10rpx 10rpx;
}
.contents-h2{
font-size: 32rpx;
font-weight: bold;
font-size: 16px;
padding: 10rpx 0rpx 10rpx 10rpx;
}
.contents-h3{
font-size: 32rpx;
font-weight: bold;
padding: 10rpx 0rpx 10rpx 10rpx;
}
.contents-h4{
font-size: 32rpx;
font-weight: bold;
padding: 10rpx 0rpx 10rpx 10rpx;
}
.contents-h5{
font-size: 32rpx;
font-weight: bold;
padding: 10rpx 0rpx 10rpx 10rpx;
}
案例效果:
我们看到有些小程序不仅仅有底部导航栏,也有顶部导航栏。博客系列页面,就来实现这一效果。
顶部导航栏官网给我们提供了一个Tab组件的案例。
1)使用步骤:
1)首先在项目目录下执行:
npm init
会生成package.json和package-lock.json两个文件
2)再执行安装
npm install @miniprogram-component-plus/tabs
安装成功后,项目目录下会生成node_modules目录
3)在开发工具中选择:工具-构建npm
项目目录下会生成miniprogram_npm目录
4)在需要使用的地方*.json引入即可
{
"usingComponents": {
"mp-tabs": "@miniprogram-component-plus/tabs"
}
}
5)接着再把官方案例中的样式也顺过来吧(weui.wxss和common.wxss)
2)blog.json
{
"usingComponents": {
"mp-tabs": "@miniprogram-component-plus/tabs"
}
}
3)pages/blog/blog.wxml
{{item.title2}}
{{item.desc}}
4)pages/blog/blog.js
// pages/blog/blog.js
Page({
data: {
tabs: [],
activeTab: 0,
},
onLoad() {
const tabs = [
{
title: '博客系列',
title2: '博客系列',
img: 'http://mmbiz.qpic.cn/sz_mmbiz_jpg/GEWVeJPFkSEV5QjxLDJaL6ibHLSZ02TIcve0ocPXrdTVqGGbqAmh5Mw9V7504dlEiatSvnyibibHCrVQO2GEYsJicPA/0?wx_fmt=jpeg',
desc: '描述1..........',
},
{
title: 'java合集',
title2: 'java合集',
img: 'http://mmbiz.qpic.cn/sz_mmbiz_png/GEWVeJPFkSHALb0g5rCc4Jf5IqDfdwhWJ43I1IvriaV5uFr9fLAuv3uxHR7DQstbIxhNXFoQEcxGzWwzQUDBd6Q/0?wx_fmt=png',
desc: '微信小程序直播系列课程持续更新中,帮助大家更好地理解、应用微信小程序直播功能。',
},
{
title: '前端知识',
title2: '常见问题和解决方案',
img: 'http://mmbiz.qpic.cn/sz_mmbiz_jpg/GEWVeJPFkSGqys4ibO2a8L9nnIgH0ibjNXfbicNbZQQYfxxUpmicQglAEYQ2btVXjOhY9gRtSTCxKvAlKFek7sRUFA/0?wx_fmt=jpeg',
desc: '提高审核质量',
},
{
title: 'python合集',
title2: '流量主小程序',
img: 'http://mmbiz.qpic.cn/sz_mmbiz_jpg/GEWVeJPFkSH2Eic4Lt0HkZeEN08pWXTticVRgyNGgBVHMJwMtRhmB0hE4m4alSuwsBk3uBBOhdCr91bZlSFbYhFg/0?wx_fmt=jpeg',
desc: '本课程共四节,将分阶段为开发者展示如何开通流量主功能、如何接入广告组件、不同类型小程序接入的建议,以及如何通过工具调优小程序变现效率。',
},
{
title: '小游戏',
title2:'2020中国高校计算机大赛',
img: 'http://mmbiz.qpic.cn/mmbiz_jpg/TcDuyasB5T3Eg34AYwjMw7xbEK2n01ekiaicPiaMInEMTkOQtuv1yke5KziaYF4MLia4IAbxlm0m5NxkibicFg4IZ92EA/0?wx_fmt=jpeg',
desc: '微信小程序应用开发赛',
},
]
this.setData({ tabs })
},
getUserProfile(e) {
},
getUserInfo(e) {
},
onTabClick(e) {
const index = e.detail.index
this.setData({
activeTab: index
})
},
onChange(e) {
const index = e.detail.index
this.setData({
activeTab: index
})
},
handleClick(e) {
}
})
案例效果:
我们还可以借助Vant Weapp库的Tab标签来实现。由于小程序目前最大支持上传2048kb,直接使用npm依赖包的方式去引用的话,会占据我更多的空间。我直接拷贝它的源码tab和tabs等组件源码来实现,这种实现难度要大些,需要自己修改一些组件的代码。
1)拷贝vant库的源码:
2)新建推荐页
{{item.title}}
{{item.temp}}
3)index.js
Page({
data: {
fatherList: [
{
tabsName: '博客系列',
list: [
{
temp: 'bkxlTemplate',
title: '博客系列'
}
]
},
{
tabsName: 'java合集',
list: [
{
temp: 'javaTemplate',
title: 'java合集'
}
]
},
{
tabsName: '前端知识',
list: [
{
temp: 'qdzsTemplate',
title: '前端知识'
}
]
},{
tabsName: '小游戏',
list: [
{
temp: 'xyxTemplate',
title: '小游戏'
}
]
},
{
tabsName: 'Python',
list: [
{
temp: 'pythonTemplate',
title: 'Python'
}
]
}
],
runbo:[
{
id:'1',
imgsrc:'/images/rb_image1.jpg'
},
{
id:'2',
imgsrc:'/images/rb_image2.jpg'
},
{
id:'3',
imgsrc:'/images/rb_image3.jpg'
}
],
active: 0
},
onLoad: function(options) {
},
onReachBottom() {
},
onReady: function() {
// 页面渲染完成
},
onShow: function() {
},
onHide: function() {
// 页面隐藏
},
onUnload: function() {
// 页面关闭
},
getAccessToken(){
//小程序中无法直接使用公众号明文的AppID和AppSecret来获取小程序的文章,所以我放弃了
var $appid="";// AppID
var $appSecret="";// AppSecret
var $url="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appid}&secret={$appSecret}";
return $url;
},
onTabChange(event) {
}
})
4)index.wxss
/**index.wxss**/
.container{
height: 100%;
width: 100%;
}
/**style="width: 100%;" mode="widthFix"
解决各种不同设备的轮播图适屏问题
**/
.banner {
width: 100%;
height: 375rpx;
}
案例效果:
1)blog.wxml
后端开发
前端开发
移动端开发
游戏开发
2)blog.wxss
/* pages/blog/blog.wxss */
@import '../common.wxss';
.page{
background-color: #FFFFFF;
height: 100%;
}
.weui-tabs-bar__wrp {
border-bottom: 1px solid #eee;
margin-top: 10px;
}
/* 默认是150高度,此处解决swiper不同滑块高度问题 */
.weui-tabs-swiper {
width: 100%;
overflow: auto;
height: 105vh;
}
.tab-content {
height: 100px;
width: 100%;
/* display: flex; */
/* justify-content: center; */
/* align-items: center; */
box-sizing: border-box;
padding: 40rpx;
}
.weui-tabs-bar__title {
margin: 0px 10px;
}
.tab-bar-title__selected {
font-size: 20px;
font-weight: bold;
}
/* 图片上下浮动 */
.bkxl_img{
animation: image 1.5s infinite;
}
@keyframes image {
0% {
transform: translate(0px, 0px);
}
50% {
transform: translate(0px, -9px);
}
100% {
transform: translate(0px, 0px);
}
}
/* 图片翻转css代码改用微信api */
/* .topImage{
-webkit-animation: transform-5 1.5s ease 500ms alternate none 1;
animation: transform-5 1.5s ease 500ms alternate none 1;
}
.topImage:hover{
-webkit-animation: transform-5 1.5s ease 500ms alternate none 1;
animation: transform-5 1.5s ease 500ms alternate none 1;
}
@-webkit-keyframes transform-5 {
from{
-webkit-transform:perspective(400px) rotateY(91deg);
transform:perspective(400px) rotateY(91deg);
}
to{
-webkit-transform:perspective(400px) rotateY(0deg);
transform:perspective(400px) rotateY(0deg);
}
}
@keyframes transform-5 {
from{
-webkit-transform:perspective(400px) rotateY(91deg);
transform:perspective(400px) rotateY(91deg);
}
to{
-webkit-transform:perspective(400px) rotateY(0deg);
transform:perspective(400px) rotateY(0deg);
}
} */
.bkxl_lw{
display: flex;
flex-direction: row;
margin-top: 50rpx;
}
.bkxl_zt_left{
margin-right: auto;
margin-left: 30rpx;
width: 300rpx;
height: 300rpx;
border:1rpx rgb(177, 170, 170) solid;
border-width: 0rpx;
border-radius: 1ch;
box-shadow:0px 2px 4px 2px #DDDDDD;
}
.bkxl_mc{
height: 90rpx;
border-radius: 0ch 0ch 1ch 1ch;
text-align: center;
line-height: 90rpx;
}
.bkxl_jb{
height: 210rpx;
background-color: #FF9E7A;
border-radius: 1ch 1ch 0ch 0ch;
}
.bkxl_tp{
width: 240rpx;
height:230rpx;
margin: auto;
}
.bkxl_img{
margin: auto;
margin-left: 30rpx;
}
.bkxl_zt_right{
margin-left: auto;
margin-right: 30rpx;
width: 300rpx;
height: 300rpx;
border:1rpx rgb(177, 170, 170) solid;
border-width: 0rpx;
border-radius: 1ch;
box-shadow:0px 2px 4px 2px #DDDDDD;
}
3)blog.js(使用wx.createAnimation)实现动画效果
// pages/blog/blog.js
Page({
data: {
tabs: [],
activeTab: 0,
animation: '',
swiperClass: 'mybk',
},
onLoad() {
const tabs = [
{
title: '博客系列',type: 'bkxl',
},
{
title: 'java合集',type: 'java',
},
{
title: '前端知识',type: 'qdzs',
},
{
title: 'python合集',type: 'python',
},
{
title: '小游戏',type: 'xyx',
},
]
this.setData({ tabs })
},
onTabClick(e) {
const index = e.detail.index
this.setData({
activeTab: index
})
},
onChange(e) {
const index = e.detail.index
this.setData({
activeTab: index
})
},
handleClick(e) {
},
onReady(){
},
transImg() {
//实现动画
this.animation = wx.createAnimation({
duration: 1000, // 动画持续时间,单位ms,默认值 400
timingFunction: 'ease', //ease 慢-块-慢,linear动画从头到尾的速度是相同的
delay: 100, //动画延迟时间
transformOrigin: '50% 50% 0', //默认50% 50% 0 (x,y,z)设置动画的基点
success: function(res) {
console.log(res)
}
})
// 沿着Y轴顺时针转动360°
this.animation.rotateY(360).step(); //.step()就是一组动画
this.setData({
//输出动画
animation: this.animation.export()
})
setTimeout(()=>{
//因为转动了360°,需要复原
this.animation.rotateY(0).step({duration:0})
this.setData({
//输出动画
animation: this.animation.export()
})
}, 1100);
},
transImg_0(){
this.transImg();
},
transImg_1(){
this.transImg();
},
transImg_2(){
this.transImg();
},
transImg_3(){
this.transImg();
}
})
案例效果:
使用wx.createAnimation实现动画效果,如果不复原,则只会触发一次(终态-动画)。在解决无法触发动画时,看到微信官方提供了关键帧动画来代替旧的 wx.createAnimation,确实方便了很多。
// pages/blog/blog.js
Page({
data: {
tabs: [],
activeTab: 0,
animation: '',
swiperClass: 'mybk',
},
onLoad() {
const tabs = [
{
title: '博客系列',type: 'bkxl',
},
{
title: 'java合集',type: 'java',
},
{
title: '前端知识',type: 'qdzs',
},
{
title: 'python合集',type: 'python',
},
{
title: '小游戏',type: 'xyx',
},
]
this.setData({ tabs })
},
onTabClick(e) {
const index = e.detail.index
this.setData({
activeTab: index
})
},
onChange(e) {
const index = e.detail.index
this.setData({
activeTab: index
})
},
handleClick(e) {
},
onReady(){
},
// transImg() {
// //实现动画
// this.animation = wx.createAnimation({
// duration: 1000, // 动画持续时间,单位ms,默认值 400
// timingFunction: 'ease', //ease 慢-块-慢,linear动画从头到尾的速度是相同的
// delay: 100, //动画延迟时间
// transformOrigin: '50% 50% 0', //默认50% 50% 0 (x,y,z)设置动画的基点
// success: function(res) {
// console.log(res)
// }
// })
// // 沿着Y轴顺时针转动360°
// this.animation.rotateY(360).step(); //.step()就是一组动画
// this.setData({
// //输出动画
// animation: this.animation.export()
// })
// setTimeout(()=>{
// //因为转动了360°,需要复原
// this.animation.rotateY(0).step({duration:0})
// this.setData({
// //输出动画
// animation: this.animation.export()
// })
// }, 1100);
// },
transImg(e){
var id = "#"+ e.currentTarget.dataset.id;
//获取data-id e.currentTarget.dataset.id
//获取id e.currentTarget.id
this.animate(id, [
{ rotateY: 0,ease: 'ease-in-out'},
{ rotateY: 360,ease: 'ease-in-out'},
], 1000, function () {
this.clearAnimation(id, {rotateY: true,ease: true }, function () {
// console.log("清除了当前动画属性")
})
}.bind(this))
}
})
案例效果:
1)2048.wxml
加载中...
2048
{{score}}
{{highscore}}
你能拿到2048吗?
新游戏
{{cell.value}}
2)2048.js
var app = getApp();
var Grid = require('./grid.js');
var Tile = require('./tile.js');
var GameManager = require('./game_manager.js');
var config = {
data: {
hidden: false,
// 游戏数据可以通过参数控制
grids: [],
over: false,
win: false,
score: 0,
highscore: 0,
overMsg: '游戏结束'
},
onLoad: function() {
this.GameManager = new GameManager(4);
this.setData({
grids: this.GameManager.setup(),
highscore: wx.getStorageSync('highscore') || 0
});
},
onReady: function() {
var that = this;
// 页面渲染完毕隐藏loading
that.setData({
hidden: true
});
},
onShow: function() {
// 页面展示
},
onHide: function() {
// 页面隐藏
},
onUnload: function() {
// 页面关闭
},
// 更新视图数据
updateView: function(data) {
// 游戏结束
if(data.over){
data.overMsg = '游戏结束';
}
// 获胜
if(data.win){
data.overMsg = '恭喜';
}
this.setData(data);
},
// 重新开始
restart: function() {
this.updateView({
grids: this.GameManager.restart(),
over: false,
won: false,
score: 0
});
},
touchStartClienX: 0,
touchStartClientY: 0,
touchEndClientX: 0,
touchEndClientY: 0,
isMultiple: false, // 多手指操作
touchStart: function(events) {
// 多指操作
this.isMultiple = events.touches.length > 1;
if (this.isMultiple) {
return;
}
var touch = events.touches[0];
this.touchStartClientX = touch.clientX;
this.touchStartClientY = touch.clientY;
},
touchMove: function(events) {
var touch = events.touches[0];
this.touchEndClientX = touch.clientX;
this.touchEndClientY = touch.clientY;
},
touchEnd: function(events) {
if (this.isMultiple) {
return;
}
var dx = this.touchEndClientX - this.touchStartClientX;
var absDx = Math.abs(dx);
var dy = this.touchEndClientY - this.touchStartClientY;
var absDy = Math.abs(dy);
if (Math.max(absDx, absDy) > 10) {
var direction = absDx > absDy ? (dx > 0 ? 1 : 3) : (dy > 0 ? 2 : 0);
var data = this.GameManager.move(direction) || {
grids: this.data.grids,
over: this.data.over,
won: this.data.won,
score: this.data.score
};
var highscore = wx.getStorageSync('highscore') || 0;
if(data.score > highscore){
wx.setStorageSync('highscore', data.score);
}
this.updateView({
grids: data.grids,
over: data.over,
won: data.won,
score: data.score,
highscore: Math.max(highscore, data.score)
});
}
}
};
Page(config);
3)2048.json
{
"navigationBarTitleText": "2048小游戏",
"backgroundColor":"#faf8ef",
"backgroundTextStyle":"dark"
}
4)2048.wxss
.container {
margin: 0;
padding: 20px 0;
background: #faf8ef;
color: #776e65;
font-family: "Helvetica Neue", Arial, sans-serif;
font-size: 18px;
}
.heading:after {
content: "";
display: block;
clear: both;
}
.title {
font-size: 80px;
font-weight: bold;
margin: 0;
display: block;
float: left;
}
.scores-container {
float: right;
text-align: right;
}
.score-container, .best-container {
position: relative;
display: inline-block;
background: #bbada0;
padding: 15px 25px;
font-size: 25px;
height: 25px;
line-height: 47px;
font-weight: bold;
border-radius: 3px;
color: white;
text-align: center;
margin: 8px 0 0 8px;
}
.score-container:after, .best-container:after {
position: absolute;
width: 100%;
top: 10px;
left: 0;
text-transform: uppercase;
font-size: 13px;
line-height: 13px;
text-align: center;
color: #eee4da;
}
.score-container .score-addition, .best-container .score-addition {
position: absolute;
right: 30px;
color: red;
font-size: 25px;
line-height: 25px;
font-weight: bold;
color: rgba(119, 110, 101, 0.9);
z-index: 100;
}
.score-container:after {
content: "Score";
}
.best-container:after {
content: "Best";
}
p {
margin-top: 0;
margin-bottom: 10px;
line-height: 1.65;
}
a {
color: #776e65;
font-weight: bold;
text-decoration: underline;
cursor: pointer;
}
strong.important {
text-transform: uppercase;
}
hr {
border: none;
border-bottom: 1px solid #d8d4d0;
margin-top: 20px;
margin-bottom: 30px;
}
.game-container {
margin-top: 40px;
position: relative;
padding: 15px;
cursor: default;
-webkit-touch-callout: none;
-ms-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-ms-touch-action: none;
touch-action: none;
background: #bbada0;
border-radius: 6px;
width: 500px;
height: 500px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.game-container .game-message {
/*display: none;*/
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(238, 228, 218, 0.5);
z-index: 100;
text-align: center;
}
.game-container .game-message p {
font-size: 60px;
font-weight: bold;
height: 60px;
line-height: 60px;
margin-top: 222px;
}
.game-container .game-message .lower {
display: block;
margin-top: 59px;
}
.game-container .game-message a {
display: inline-block;
background: #8f7a66;
border-radius: 3px;
padding: 0 20px;
text-decoration: none;
color: #f9f6f2;
height: 40px;
line-height: 42px;
margin-left: 9px;
}
.game-container .game-message .keep-playing-button {
display: none;
}
.game-container .game-message.game-won {
background: rgba(237, 194, 46, 0.5);
color: #f9f6f2;
}
.game-container .game-message.game-won .keep-playing-button {
display: inline-block;
}
.game-container .game-message.game-won, .game-container .game-message.game-over {
display: block;
}
.grid-container {
position: absolute;
z-index: 1;
}
.grid-row {
margin-bottom: 15px;
}
.grid-row:last-child {
margin-bottom: 0;
}
.grid-row:after {
content: "";
display: block;
clear: both;
}
.grid-cell {
width: 106.25px;
height: 106.25px;
margin-right: 15px;
float: left;
border-radius: 3px;
background: rgba(238, 228, 218, 0.35);
}
.grid-cell:last-child {
margin-right: 0;
}
.tile-container {
position: absolute;
z-index: 2;
}
.tile, .tile .tile-inner {
width: 107px;
height: 107px;
line-height: 107px;
}
.tile.tile-position-1-1 {
-webkit-transform: translate(0px, 0px);
-moz-transform: translate(0px, 0px);
-ms-transform: translate(0px, 0px);
transform: translate(0px, 0px);
}
.tile.tile-position-1-2 {
-webkit-transform: translate(0px, 121px);
-moz-transform: translate(0px, 121px);
-ms-transform: translate(0px, 121px);
transform: translate(0px, 121px);
}
.tile.tile-position-1-3 {
-webkit-transform: translate(0px, 242px);
-moz-transform: translate(0px, 242px);
-ms-transform: translate(0px, 242px);
transform: translate(0px, 242px);
}
.tile.tile-position-1-4 {
-webkit-transform: translate(0px, 363px);
-moz-transform: translate(0px, 363px);
-ms-transform: translate(0px, 363px);
transform: translate(0px, 363px);
}
.tile.tile-position-2-1 {
-webkit-transform: translate(121px, 0px);
-moz-transform: translate(121px, 0px);
-ms-transform: translate(121px, 0px);
transform: translate(121px, 0px);
}
.tile.tile-position-2-2 {
-webkit-transform: translate(121px, 121px);
-moz-transform: translate(121px, 121px);
-ms-transform: translate(121px, 121px);
transform: translate(121px, 121px);
}
.tile.tile-position-2-3 {
-webkit-transform: translate(121px, 242px);
-moz-transform: translate(121px, 242px);
-ms-transform: translate(121px, 242px);
transform: translate(121px, 242px);
}
.tile.tile-position-2-4 {
-webkit-transform: translate(121px, 363px);
-moz-transform: translate(121px, 363px);
-ms-transform: translate(121px, 363px);
transform: translate(121px, 363px);
}
.tile.tile-position-3-1 {
-webkit-transform: translate(242px, 0px);
-moz-transform: translate(242px, 0px);
-ms-transform: translate(242px, 0px);
transform: translate(242px, 0px);
}
.tile.tile-position-3-2 {
-webkit-transform: translate(242px, 121px);
-moz-transform: translate(242px, 121px);
-ms-transform: translate(242px, 121px);
transform: translate(242px, 121px);
}
.tile.tile-position-3-3 {
-webkit-transform: translate(242px, 242px);
-moz-transform: translate(242px, 242px);
-ms-transform: translate(242px, 242px);
transform: translate(242px, 242px);
}
.tile.tile-position-3-4 {
-webkit-transform: translate(242px, 363px);
-moz-transform: translate(242px, 363px);
-ms-transform: translate(242px, 363px);
transform: translate(242px, 363px);
}
.tile.tile-position-4-1 {
-webkit-transform: translate(363px, 0px);
-moz-transform: translate(363px, 0px);
-ms-transform: translate(363px, 0px);
transform: translate(363px, 0px);
}
.tile.tile-position-4-2 {
-webkit-transform: translate(363px, 121px);
-moz-transform: translate(363px, 121px);
-ms-transform: translate(363px, 121px);
transform: translate(363px, 121px);
}
.tile.tile-position-4-3 {
-webkit-transform: translate(363px, 242px);
-moz-transform: translate(363px, 242px);
-ms-transform: translate(363px, 242px);
transform: translate(363px, 242px);
}
.tile.tile-position-4-4 {
-webkit-transform: translate(363px, 363px);
-moz-transform: translate(363px, 363px);
-ms-transform: translate(363px, 363px);
transform: translate(363px, 363px);
}
.tile {
position: absolute;
-webkit-transition: 100ms ease-in-out;
-moz-transition: 100ms ease-in-out;
transition: 100ms ease-in-out;
-webkit-transition-property: -webkit-transform;
-moz-transition-property: -moz-transform;
transition-property: transform;
}
.tile .tile-inner {
border-radius: 3px;
background: #eee4da;
text-align: center;
font-weight: bold;
z-index: 10;
font-size: 55px;
}
.tile.tile-2 .tile-inner {
background: #eee4da;
box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0);
}
.tile.tile-4 .tile-inner {
background: #ede0c8;
box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0);
}
.tile.tile-8 .tile-inner {
color: #f9f6f2;
background: #f2b179;
}
.tile.tile-16 .tile-inner {
color: #f9f6f2;
background: #f59563;
}
.tile.tile-32 .tile-inner {
color: #f9f6f2;
background: #f67c5f;
}
.tile.tile-64 .tile-inner {
color: #f9f6f2;
background: #f65e3b;
}
.tile.tile-128 .tile-inner {
color: #f9f6f2;
background: #edcf72;
box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.2381), inset 0 0 0 1px rgba(255, 255, 255, 0.14286);
font-size: 45px;
}
@media screen and (max-width:520px) {
.tile.tile-128 .tile-inner {
font-size: 25px;
}
}
.tile.tile-256 .tile-inner {
color: #f9f6f2;
background: #edcc61;
box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.31746), inset 0 0 0 1px rgba(255, 255, 255, 0.19048);
font-size: 45px;
}
@media screen and (max-width:520px) {
.tile.tile-256 .tile-inner {
font-size: 25px;
}
}
.tile.tile-512 .tile-inner {
color: #f9f6f2;
background: #edc850;
box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.39683), inset 0 0 0 1px rgba(255, 255, 255, 0.2381);
font-size: 45px;
}
@media screen and (max-width:520px) {
.tile.tile-512 .tile-inner {
font-size: 25px;
}
}
.tile.tile-1024 .tile-inner {
color: #f9f6f2;
background: #edc53f;
box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.47619), inset 0 0 0 1px rgba(255, 255, 255, 0.28571);
font-size: 35px;
}
@media screen and (max-width:520px) {
.tile.tile-1024 .tile-inner {
font-size: 15px;
}
}
.tile.tile-2048 .tile-inner {
color: #f9f6f2;
background: #edc22e;
box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.55556), inset 0 0 0 1px rgba(255, 255, 255, 0.33333);
font-size: 35px;
}
@media screen and (max-width:520px) {
.tile.tile-2048 .tile-inner {
font-size: 15px;
}
}
.tile.tile-super .tile-inner {
color: #f9f6f2;
background: #3c3a32;
font-size: 30px;
}
@media screen and (max-width:520px) {
.tile.tile-super .tile-inner {
font-size: 10px;
}
}
.tile-merged .tile-inner {
z-index: 20;
}
.above-game:after {
content: "";
display: block;
clear: both;
}
.game-intro {
float: left;
line-height: 42px;
margin-bottom: 0;
}
.restart-button {
display: inline-block;
background: #8f7a66;
border-radius: 3px;
padding: 0 20px;
text-decoration: none;
color: #f9f6f2;
height: 40px;
line-height: 42px;
display: block;
text-align: center;
margin-left: auto;
}
.game-explanation {
margin-top: 50px;
}
@media screen and (max-width:520px) {
html, body {
font-size: 15px;
}
body {
margin: 20px 0;
padding: 0 20px;
}
.title {
font-size: 27px;
margin-top: 15px;
}
/*.container {
width: 280px;
margin: 0 auto;
}*/
.score-container, .best-container {
margin-top: 0;
padding: 15px 10px;
min-width: 40px;
}
.heading {
margin-bottom: 10px;
}
.game-intro {
width: 55%;
display: block;
box-sizing: border-box;
line-height: 1.65;
}
.restart-button {
width: 42%;
padding: 0;
display: block;
box-sizing: border-box;
margin-top: 2px;
}
.game-container {
margin-top: 17px;
position: relative;
padding: 10px;
cursor: default;
-webkit-touch-callout: none;
-ms-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-ms-touch-action: none;
touch-action: none;
background: #bbada0;
border-radius: 6px;
width: 280px;
height: 280px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.game-container .game-message {
display: none;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(238, 228, 218, 0.5);
z-index: 100;
text-align: center;
}
.game-container .game-message .over-msg {
display: block;
font-size: 30px;
font-weight: bold;
height: 30px;
line-height: 30px;
/*margin-top: 222px;*/
margin-top: 59px;
}
.game-container .game-message .lower {
display: block;
margin-top: 59px;
}
.game-container .game-message .retry-button {
display: inline-block;
background: #8f7a66;
border-radius: 3px;
padding: 0 20px;
text-decoration: none;
color: #f9f6f2;
height: 40px;
line-height: 42px;
margin-left: 9px;
}
.game-container .game-message .keep-playing-button {
display: none;
}
.game-container .game-message.game-won {
background: rgba(237, 194, 46, 0.5);
color: #f9f6f2;
}
.game-container .game-message.game-won .keep-playing-button {
display: inline-block;
}
.game-container .game-message.game-won, .game-container .game-message.game-over {
display: block;
}
.grid-container {
position: absolute;
z-index: 1;
}
.grid-row {
margin-bottom: 10px;
}
.grid-row:last-child {
margin-bottom: 0;
}
.grid-row:after {
content: "";
display: block;
clear: both;
}
.grid-cell {
width: 57.5px;
height: 57.5px;
margin-right: 10px;
float: left;
border-radius: 3px;
background: rgba(238, 228, 218, 0.35);
}
.grid-cell:last-child {
margin-right: 0;
}
.tile, .tile .tile-inner {
width: 58px;
height: 58px;
line-height: 58px;
}
.tile.tile-position-1-1 {
-webkit-transform: translate(0px, 0px);
-moz-transform: translate(0px, 0px);
-ms-transform: translate(0px, 0px);
transform: translate(0px, 0px);
}
.tile.tile-position-1-2 {
-webkit-transform: translate(0px, 67px);
-moz-transform: translate(0px, 67px);
-ms-transform: translate(0px, 67px);
transform: translate(0px, 67px);
}
.tile.tile-position-1-3 {
-webkit-transform: translate(0px, 135px);
-moz-transform: translate(0px, 135px);
-ms-transform: translate(0px, 135px);
transform: translate(0px, 135px);
}
.tile.tile-position-1-4 {
-webkit-transform: translate(0px, 202px);
-moz-transform: translate(0px, 202px);
-ms-transform: translate(0px, 202px);
transform: translate(0px, 202px);
}
.tile.tile-position-2-1 {
-webkit-transform: translate(67px, 0px);
-moz-transform: translate(67px, 0px);
-ms-transform: translate(67px, 0px);
transform: translate(67px, 0px);
}
.tile.tile-position-2-2 {
-webkit-transform: translate(67px, 67px);
-moz-transform: translate(67px, 67px);
-ms-transform: translate(67px, 67px);
transform: translate(67px, 67px);
}
.tile.tile-position-2-3 {
-webkit-transform: translate(67px, 135px);
-moz-transform: translate(67px, 135px);
-ms-transform: translate(67px, 135px);
transform: translate(67px, 135px);
}
.tile.tile-position-2-4 {
-webkit-transform: translate(67px, 202px);
-moz-transform: translate(67px, 202px);
-ms-transform: translate(67px, 202px);
transform: translate(67px, 202px);
}
.tile.tile-position-3-1 {
-webkit-transform: translate(135px, 0px);
-moz-transform: translate(135px, 0px);
-ms-transform: translate(135px, 0px);
transform: translate(135px, 0px);
}
.tile.tile-position-3-2 {
-webkit-transform: translate(135px, 67px);
-moz-transform: translate(135px, 67px);
-ms-transform: translate(135px, 67px);
transform: translate(135px, 67px);
}
.tile.tile-position-3-3 {
-webkit-transform: translate(135px, 135px);
-moz-transform: translate(135px, 135px);
-ms-transform: translate(135px, 135px);
transform: translate(135px, 135px);
}
.tile.tile-position-3-4 {
-webkit-transform: translate(135px, 202px);
-moz-transform: translate(135px, 202px);
-ms-transform: translate(135px, 202px);
transform: translate(135px, 202px);
}
.tile.tile-position-4-1 {
-webkit-transform: translate(202px, 0px);
-moz-transform: translate(202px, 0px);
-ms-transform: translate(202px, 0px);
transform: translate(202px, 0px);
}
.tile.tile-position-4-2 {
-webkit-transform: translate(202px, 67px);
-moz-transform: translate(202px, 67px);
-ms-transform: translate(202px, 67px);
transform: translate(202px, 67px);
}
.tile.tile-position-4-3 {
-webkit-transform: translate(202px, 135px);
-moz-transform: translate(202px, 135px);
-ms-transform: translate(202px, 135px);
transform: translate(202px, 135px);
}
.tile.tile-position-4-4 {
-webkit-transform: translate(202px, 202px);
-moz-transform: translate(202px, 202px);
-ms-transform: translate(202px, 202px);
transform: translate(202px, 202px);
}
.tile .tile-inner {
font-size: 35px;
}
.game-message p {
font-size: 30px !important;
height: 30px !important;
line-height: 30px !important;
margin-top: 90px !important;
}
.game-message .lower {
margin-top: 30px !important;
}
}
5)grid.js
function Grid(size) {
this.size = size;
this.cells = this.empty();
}
Grid.prototype = {
// 构造一个空的矩阵[[null,..,size.length],[]]
empty: function() {
var cells = [];
for (var x = 0; x < this.size; x++) {
var row = cells[x] = [];
for (var y = 0; y < this.size; y++) {
row.push(null);
}
}
// [[{x:0,y:0},{x:0,y:1}],[]]
return cells;
},
// 在空格子中随机挑选出一个格子
randomAvailableCell: function() {
var cells = this.availableCells();
// 存在可填充的格子
if (cells.length) {
return cells[Math.floor(Math.random() * cells.length)];
}
},
// 获取可填充的格子坐标
availableCells: function() {
var cells = [];
for (var i = 0; i < this.size; i++) {
for (var j = 0; j < this.size; j++) {
// 当前格子无内容
if (!this.cells[i][j]) {
cells.push({
x: i,
y: j
});
}
}
}
return cells;
},
// 是否存在空单元格
cellsAvailable: function() {
return !!this.availableCells().length;
},
cellAvailable: function(cell) {
return !this.cellContent(cell);
},
insertTile: function(tile) {
this.cells[tile.x][tile.y] = tile;
},
removeTile: function(tile) {
this.cells[tile.x][tile.y] = null;
},
/*
* 获取单元格内容
* @param {object} cell {x:0,y:0} 单元格坐标
*/
cellContent: function(cell) {
if (this.withinBounds(cell)) {
return this.cells[cell.x][cell.y] || null;
} else {
return null;
}
},
/*
* 空单元格,格子还未填充数字
*/
emptyCell: function(cell) {
return !this.cellContent(cell);
},
withinBounds: function(cell) {
return cell.x >= 0 && cell.x < this.size && cell.y >= 0 && cell.y < this.size;
},
eachCell: function(callback) {
for (var x = 0; x < this.size; x++) {
for (var y = 0; y < this.size; y++) {
callback(x, y, this.cells[x][y]);
}
}
}
}
module.exports = Grid;
6)game_manager.js
var Grid = require('./grid.js');
var Tile = require('./tile.js');
function GameManager(size) {
this.size = size;
this.startTiles = 2;
}
GameManager.prototype = {
setup: function() {
this.grid = new Grid(this.size);
this.score = 0;
this.over = false;
this.won = false;
this.addStartTiles();
return this.grid.cells;
},
// 初始化数据
addStartTiles: function() {
for (var x = 0; x < this.startTiles; x++) {
this.addRandomTiles();
}
},
// 在一个随机单元格中随机填充2或4
addRandomTiles: function() {
if (this.grid.cellsAvailable()) {
var value = Math.random() < 0.9 ? 2 : 4;
var cell = this.grid.randomAvailableCell();
var tile = new Tile(cell, value);
this.grid.insertTile(tile); // 插入一个单元格
}
},
actuate: function() {
return {
grids: this.grid.cells,
over: this.over,
won: this.won,
score: this.score
}
},
// 偏移向量
getVector: function(direction) {
var map = {
0: { // 上
x: -1,
y: 0
},
1: { // 右
x: 0,
y: 1
},
2: { // 下
x: 1,
y: 0
},
3: { // 左
x: 0,
y: -1
}
};
return map[direction];
},
buildTraversals: function(vector) {
var traversals = {
x: [],
y: []
};
for (var pos = 0; pos < this.size; pos++) {
traversals.x.push(pos);
traversals.y.push(pos);
}
// 为什么要加这个,看findFarthestTail
if (vector.x === 1) {
// 向右时
traversals.x = traversals.x.reverse();
}
if (vector.y === 1) {
// 向下
traversals.y = traversals.y.reverse();
}
return traversals;
},
// 把当前单元格挪至下一个可放置的区域
moveTile: function(tile, cell) {
this.grid.cells[tile.x][tile.y] = null;
this.grid.cells[cell.x][cell.y] = tile;
tile.updatePosition(cell);
},
// 特定方向移动单元格
move: function(direction) {
// 0: up, 1: right, 2: down, 3: left
var self = this;
var vector = this.getVector(direction);
var traversals = this.buildTraversals(vector);
var cell;
var tile;
var moved = false;
self.prepareTiles();
traversals.x.forEach(function(x) {
traversals.y.forEach(function(y) {
// console.log('x:', x, 'y:', y);
cell = {
x: x,
y: y
};
tile = self.grid.cellContent(cell);
if (tile) { // 单元格有内容
var positions = self.findFarthestTail(cell, vector);
var next = self.grid.cellContent(positions.next);
if (next && next.value === tile.value && !next.mergedFrom) {
// 当前格子和其移动方向格子内容相同,需要合并
var merged = new Tile(positions.next, tile.value * 2); // 合并后的格子信息
merged.mergedFrom = [tile, next];
self.grid.insertTile(merged); // 把合并的盒子插入到当前格子数据中
self.grid.removeTile(tile); // 删除当前格子内容
tile.updatePosition(positions.next);
self.score += merged.value;
if (merged.value === 2048) self.won = true;
} else {
self.moveTile(tile, positions.farthest);
}
// 是否从当前位置移到当前位置
if (!self.positionsEqual(cell, tile)) {
moved = true;
}
}
});
});
if (moved) {
this.addRandomTiles();
if (!this.movesAvailable()) {
this.over = true;
}
return this.actuate();
}
// return this.grid.cells
},
prepareTiles: function() {
var tile;
for (var x = 0; x < this.size; x++) {
for (var y = 0; y < this.size; y++) {
tile = this.grid.cells[x][y];
if (tile) {
tile.mergedFrom = null;
tile.savePosition();
}
}
}
},
positionsEqual: function(first, second) {
return first.x === second.x && first.y === second.y;
},
movesAvailable: function() {
return this.grid.cellsAvailable() || this.tileMatchesAvailable();
},
tileMatchesAvailable: function() {
var self = this;
var tile;
for (var x = 0; x < this.size; x++) {
for (var y = 0; y < this.size; y++) {
tile = this.grid.cellContent({ x: x, y: y });
if (tile) {
for (var direction = 0; direction < 4; direction++) {
var vector = self.getVector(direction);
var cell = { x: x + vector.x, y: y + vector.y };
var other = self.grid.cellContent(cell);
if (other && other.value === tile.value) {
return true;
}
}
}
}
}
return false;
},
// 找到当前偏移方向存在最远的空单元格
// 如:向右偏移,那么返回当前行最靠右的空单元格及其右侧距离其最远的一个格子,向下一样
findFarthestTail: function(cell, vector) {
var previous;
// 当前单元格在范围内且存在可用单元格
do {
previous = cell;
cell = {
x: previous.x + vector.x,
y: previous.y + vector.y
};
}
while (this.grid.withinBounds(cell) && this.grid.emptyCell(cell));
return {
farthest: previous,
next: cell
}
},
// 重新开始
restart: function() {
return this.setup();
}
}
module.exports = GameManager;
7)tile.js
function Tile(position, value) {
this.x = position.x;
this.y = position.y;
this.value = value || 2;
this.previousPosition = null;
this.mergedFrom = null;
}
Tile.prototype = {
// 记录格子上次的位置
savePosition: function() {
this.previousPosition = {
x: this.x,
y: this.y
};
},
// 更新当前格子的位置
updatePosition: function(position) {
this.x = position.x;
this.y = position.y;
},
serialize: function() {
return {
position: {
x: this.x,
y: this.y
},
value: this.value
};
}
}
module.exports = Tile;
8)blog.wxml
案例效果:
因非认证的个人小程序的原因,关于我的页面也只能草草了事。