赞
踩
百度百科给出的定义:
微信小程序,英文名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)一个小程序页面由四个文件组成
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
- <!--pages/person/person.wxml-->
- <view class="container">
- <view>
- <text>flex-direction: row——横向布局</text>
- </view>
- <view class="flex-wrp">
- <view class="flex-item demo-text-1">A</view>
- <view class="flex-item demo-text-2">B</view>
- <view class="flex-item demo-text-3">C</view>
- </view>
- </view>
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
- <!--pages/person/person.wxml-->
- <view class="container">
- <view>
- <text>view-hover:视图点击生态-不阻止父类</text>
- </view>
- <view class="view-parent" hover-class="view-hover">我是父类容器
- <view class="view-son" hover-class="view-hover">我是子类容器</view>
- </view>
- <view>
- <text>view-hover:视图点击生态-阻止父类</text>
- </view>
- <view class="view-parent" hover-class="view-hover">我是父类容器
- <view class="view-son" hover-class="view-hover" hover-stop-propagation>我是子类容器</view>
- </view>
- </view>
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
- <!--pages/person/person.wxml-->
- <view class="container">
- <view>
- <text>scroll-view:可滚动视图-纵向滚动</text>
- </view>
- <!--scroll-y 属性 允许纵向滚动-->
- <scroll-view scroll-y class="scrolly">
- <view class="scroll-y-item bg_green">A</view>
- <view class="scroll-y-item bg_red">B</view>
- <view class="scroll-y-item bg_blue">C</view>
- <view class="scroll-y-item bg_yellow">D</view>
- </scroll-view>
- </view>
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
- <!--pages/person/person.wxml-->
- <view class="container">
- <view>
- <text>scroll-view:可滚动视图-横向滚动</text>
- </view>
- <!--scroll-x 属性 允许横向滚动-->
- <scroll-view scroll-x class="scrollx">
- <view class="scroll-x-item bg_green">A</view>
- <view class="scroll-x-item bg_red">B</view>
- <view class="scroll-x-item bg_blue">C</view>
- <view class="scroll-x-item bg_yellow">D</view>
- </scroll-view>
- </view>
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
- <!--pages/person/person.wxml-->
- <view class="swiper-container">
- <view>
- <text>swiper:滑块视图容器-轮播图</text>
- </view>
- <swiper class="swiper" indicator-dots autoplay interval="3000" indicator-color="white">
- <swiper-item class="swiper-item bg_green">A</swiper-item>
- <swiper-item class="swiper-item bg_red">B</swiper-item>
- <swiper-item class="swiper-item bg_blue">C</swiper-item>
- <swiper-item class="swiper-item bg_yellow">D</swiper-item>
- </swiper>
- </view>
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组件测试
- <!--pages/person/person.wxml-->
- <view class="container" >
- 我是view组件
- <text>我是text组件,测试超长是否自动换行的哈哈哈哈哈哈哈哈哈</text>
-
- <text user-select>我是text组件,支持长按选中,\n测试手动换行</text>
-
- </view>
案例效果:
表单组件有: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、打开授权页面、获取用户头像等)。
- <!--pages/person/person.wxml-->
- <view class="container" >
- <!--全局设置里的app.json中的"style": "v2"是新版样式,删除掉就是旧版样式-->
- <label>我是button组件:</label>
- <button>普通按钮</button>
- <button type="primary">主色调按钮</button>
- <!--size设置按钮的大小-->
- <button type="warn" size="mini">警告按钮</button>
- <!--plain镂空,背景色透明-->
- <button plain size="mini">警告按钮</button>
- <!--open-type="contact"打开客户会话-->
- <button type="primary" size="mini" open-type="contact">打开客户会话
- </button>
- </view>
案例效果:新版样式
旧版样式:
媒体组件主要是用来处理图片、视频、音频的组件。主要包括:
组件名称 | 组件说明 |
---|---|
camera | 系统相机,如:扫描二维码(微信6.7.3版本) |
image | 图片 |
live-player | 实时音视频播放 |
live-pusher | 实时音视频录制 |
video | 视频 |
viop-room | 多人音频视频对话 |
3.3.4.1、camera组件
camera组件可以调用系统相机,支持拍照、扫码二维码功能。同一页面只能插入一个 camera
组件。
person.wxml
- <!--pages/person/person.wxml-->
- <view class="container" >
- <camera device-position="back" flash="off" binderror="error" style="width: 100%; height: 300px;"></camera>
- <button type="primary" bindtap="takePhoto">拍照</button>
- <view>预览</view>
- <image mode="widthFix" src="{{src}}"></image>
-
- </view>
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属性来指定图片裁剪和缩放模式。
- <!--pages/person/person.wxml-->
- <view class="page" style="margin-left: 1rem;">
- <view class="page__hd">
- <text class="page__title">image</text>
- <text class="page__desc">图片</text>
- </view>
- <view class="page__bd">
- <view class="section section_gap" wx:for="{{array}}" wx:for-item="item">
- <view class="section__title">{{item.text}}</view>
- <view class="section__ctn">
- <image style="width: 150px; height: 180px; background-color: #eeeeee;" mode="{{item.mode}}" src="{{src}}"></image>
- </view>
- </view>
- </view>
- </view>
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中使用数据:
- <!--Vue的动态绑定 :src-->
- <img :src="imgPath"></img>
- <!--小程序的动态绑定-->
- <image src="{{imgPath}}"></image>
例如:
blog.wxml
- <!--pages/blog/blog.wxml-->
- <image src="{{imgPath}}"></image>
blog.js
- // pages/blog/blog.js
- Page({
-
- /**
- * 页面的初始数据
- */
- data: {
- imgPath:"/images/image_mm.jpg"
- }
- })
案例效果:
2.3.1.3、列表渲染
- <!--Vue-->
- <li v-for="item in items">
- {{ item.message }}
- </li>
- <!--wxml-->
- <view wx:for="{{array}}"> {{item}} </view>
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
- <span style="color:#000000"><span style="background-color:#ffffff"><code>Page({
-
- <span style="color:#5c6370"><em>/**
- * 页面的初始数据
- */</em></span>
- <span style="color:#d19a66">data</span>: {
- <span style="color:#d19a66">array1</span>:[<span style="color:#d19a66">1</span>,<span style="color:#d19a66">2</span>,<span style="color:#d19a66">3</span>,<span style="color:#d19a66">4</span>],
- <span style="color:#d19a66">array2</span>:[
- {<span style="color:#d19a66">id</span>:<span style="color:#98c379">"1"</span>,<span style="color:#d19a66">username</span>:<span style="color:#98c379">"a"</span>},
- {<span style="color:#d19a66">id</span>:<span style="color:#98c379">"2"</span>,<span style="color:#d19a66">username</span>:<span style="color:#98c379">"b"</span>},
- {<span style="color:#d19a66">id</span>:<span style="color:#98c379">"3"</span>,<span style="color:#d19a66">username</span>:<span style="color:#98c379">"c"</span>}
- ]
- }
- })
- </code></span></span>
案例效果:
- 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控制元素的显示和隐藏。
- <view wx:if="{{condition}}"> true </view>
-
- <view hidden="{{condition}}">false</view>
block wx:if
- <block wx:if="{{true}}">
- <view> view1 </view>
- <view> view2 </view>
- </block>
<block/>
并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性。
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
- <view class="container">
- <page class="page">
- <!-- 轮播图 -->
- <swiper class="banner" indicator-dots="true" autoplay="true" interval="3000" duration="1000" style="margin-top: 1rpx;">
- <swiper-item wx:for="{{runbo}}" wx:key="id" class="banner-item" >
- <image src="{{item.imgsrc}}" style="width: 100%;" mode="widthFix"></image>
- </swiper-item>
- </swiper>
- </page>
- </view>
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
- <!--index.wxml-->
- <view class="container">
- <page class="page">
- <!-- 首页 -->
- <!-- <scroll-view scroll-into-view="item_0">
- <view class="weui-tabs-bar__wrp" style="margin-top: 3rpx;">
- <view class="weui-tabs-bar__content">
- <view class="weui-tabs-bar__title tab-bar-title__selected" style="border-bottom-color: #07c160">
- <text>首页</text>
- </view>
- </view>
- </view>
- </scroll-view> -->
- <!-- 轮播图 -->
- <swiper class="banner" indicator-dots="true" autoplay="true" interval="3000" duration="1000" style="margin-top: 1rpx;">
- <swiper-item wx:for="{{runbo}}" wx:key="id" class="banner-item" >
- <image src="{{item.imgsrc}}" style="width: 100%;" mode="widthFix"></image>
- </swiper-item>
- </swiper>
- <!-- 最新 -->
- <view class="article-follow">—— 最新 ——</view>
-
- <!-- 文章列表 -->
- <view class="article-card" wx:for="{{zx_articles}}" wx:for-item="articles" wx:key="id">
- <navigator class="content" url="{{articles.contentPath}}">
- <image class="cover" src="{{articles.imageUrl}}" mode="widthFix"></image>
- <text class="title">{{articles.title}}</text>
- <text class="desc">{{articles.content}}</text>
- </navigator>
- <view class="operation">
- <text class="date">{{articles.createTime}}</text>
- </view>
- </view>
-
- <!-- 推荐 -->
- <view class="article-follow">—— 推荐 ——</view>
-
- <!-- 文章列表 -->
- <view class="article-card" wx:for="{{tj_articles}}" wx:for-item="articles" wx:key="id">
- <navigator class="content" url="{{articles.contentPath}}">
- <image class="cover" src="{{articles.imageUrl}}" mode="widthFix"></image>
- <text class="title">{{articles.title}}</text>
- <text class="desc">{{articles.content}}</text>
- </navigator>
- <view class="operation">
- <text class="date">{{articles.createTime}}</text>
- </view>
- </view>
- </page>
-
- </view>

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
- <!--入口模版-->
- <template name="articleDetail">
- <view class="container">
- <!-- 标题 -->
- <view class="article-title">{{articlesData.title}}</view>
- <!-- 作者 -->
- <view class="article-sub-title">
- <text style="padding-right:21px">{{articlesData.author}}</text>
- <text>{{articlesData.createTime}}</text>
- </view>
- <view class="article-common-content">
- <view class="commonContent">{{articlesData.commonContent}}</view>
- </view>
- <!-- 文章内容 -->
- <template is="articlesContent" data="{{articlesData}}" />
- </view>
- </template>
-
- <!-- 文章内容 -->
- <template name="articlesContent">
- <!-- 内容解析 -->
- <block wx:for="{{articlesData.content}}" wx:key="index" wx:for-item="contents">
- <!-- 目录 -->
- <block wx:if="{{contents.type == 'h1' || contents.type == 'h2' || contents.type == 'h3' || contents.type == 'h4'|| contents.type == 'h5'}}">
- <view class="contents-{{contents.type}}">
- <text>{{contents.text}}</text>
- </view>
- </block>
-
- <block wx:if="{{contents.type == 'p'}}">
- <view class="contents-{{contents.type}}">
- <text>{{contents.text}}</text>
- </view>
- </block>
-
- <block wx:if="{{contents.type == 'img'}}">
- <view class="contents-{{contents.type}}">
- <image src="{{contents.src}}"></image>
- </view>
- </block>
- </block>
- </template>

2)detail.wxml
- <!--pages/detail/detail.wxml-->
- <import src="./template/articles" />
- <template is="articleDetail" data="{{articlesData}}"></template>
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
- <view class="container">
- <view class="page">
- <mp-tabs
- tabs="{{tabs}}"
- activeTab="{{activeTab}}"
- swiperClass="weui-tabs-swiper"
- bindtabclick="onTabClick"
- bindchange="onChange"
- activeClass="tab-bar-title__selected"
-
- >
- <block wx:for="{{tabs}}" wx:key="title">
- <view class="tab-content" data-set="{{item}}" slot="tab-content-{{index}}" bind:tap="handleClick" >
- <image src="{{item.img}}" mode="widthFix"></image>
- <view class="item-title">
- {{item.title2}}
- </view>
- <view class="item-desc">
- {{item.desc}}
- </view>
- </view>
- </block>
- </mp-tabs>
- </view>
-
- </view>

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)新建推荐页
- <!--index.wxml-->
- <!--在公共页面新建如下页面,待后续实现-->
- <import src="../../lib/common/bkxlTemplate.wxml"/>
- <import src="../../lib/common/javaTemplate.wxml"/>
- <import src="../../lib/common/qdzsTemplate.wxml"/>
- <import src="../../lib/common/xyxTemplate.wxml"/>
- <import src="../../lib/common/pythonTemplate.wxml"/>
-
- <view class="con">
- <van-tabs swipeable animated tab-class="tab-box" title-active-color="#A6894E" color="#DFBF7D" bind:change="onTabChange" active="{{ active }}">
- <!--可以单独添加上面的导航栏-->
- <van-tab title="推荐" class="tab">
- <swiper class="banner" indicator-dots="true" autoplay="true" interval="3000" duration="1000">
- <swiper-item wx:for="{{runbo}}" wx:key="id" class="banner-item" >
- <image src="{{item.imgsrc}}" mode="scaleToFill" style="width: 100%;" mode="widthFix"></image>
- </swiper-item>
- </swiper>
- </van-tab>
-
-
- <!--也可以在index.js中配置使用模板引入-->
- <van-tab title="{{item.tabsName}}" wx:for="{{fatherList}}" wx:key="index" wx:for-index="index" class="tab">
- <view class="" wx:for="{{item.list}}" wx:key="idx" wx:for-index="idx" class="van-item">
- <view>{{item.title}}</view>
- <view>{{item.temp}}</view>
- <template is='{{item.temp}}'/>
- </view>
- </van-tab>
- </van-tabs>
- </view>

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
- <!--pages/blog/blog.wxml-->
- <view class="container">
- <view class="page">
- <mp-tabs
- tabs="{{tabs}}"
- activeTab="{{activeTab}}"
- swiperClass="weui-tabs-swiper"
- bindtabclick="onTabClick"
- bindchange="onChange"
- activeClass="tab-bar-title__selected"
- >
- <block wx:for="{{tabs}}" wx:key="title">
- <view class="tab-contentsss" data-set="{{item}}" slot="tab-content-{{index}}">
- <view style="width: 100%;white-space: nowrap;">
- <image src="/images/rb_image1.jpg" style="width: 100%;height:375rpx;" mode="widthFix"></image>
- </view>
- <!-- 正常应该做公共页面,使用模板引入的方式能减少挺多代码 -->
- <!-- 博客系列展示 -->
- <block wx:if="{{item.type === 'bkxl'}}">
- <view class="bkxl_lw">
- <view class="bkxl_zt_left topImage" bindtap="transImg" data-id="bkxl_hd">
- <view style="background-color: #FF9E7A;" class="bkxl_jb">
- <view class="bkxl_loge_lf" id="bkxl_hd">
- <view class="bkxl_img">
- <image class="bkxl_img" animation="{{animation}}" src="/images/bkxl_hd.png" class="bkxl_tp" ></image>
- </view>
- </view>
- </view>
- <view class="bkxl_mc">后端开发</view>
- </view>
-
- <view class="bkxl_zt_right topImage" bindtap="transImg" data-id="bkxl_qd">
- <view style="background-color: #41fc89;" class="bkxl_jb">
- <view class="bkxl_loge_lf" id="bkxl_qd">
- <view class="bkxl_img">
- <image animation="{{animation}}" src="/images/bkxl_qd.png" class="bkxl_tp" ></image>
- </view>
- </view>
- </view>
- <view class="bkxl_mc">前端开发</view>
- </view>
- </view>
-
- <view class="bkxl_lw">
- <view class="bkxl_zt_left topImage" bindtap="transImg" data-id="bkxl_yd">
- <view style="background-color: #8ac5ec;" class="bkxl_jb">
- <view class="bkxl_loge_lf" id="bkxl_yd">
- <view class="bkxl_img">
- <image animation="{{animation}}" src="/images/bkxl_ydd.png" class="bkxl_tp" ></image>
- </view>
- </view>
- </view>
- <view class="bkxl_mc">移动端开发</view>
- </view>
-
- <view class="bkxl_zt_right topImage" bindtap="transImg" data-id="bkxl_yx">
- <view style="background-color: #dafa68;" class="bkxl_jb">
- <view class="bkxl_loge_lf" id="bkxl_yx">
- <view class="bkxl_img">
- <image animation="{{animation}}" src="/images/bkxl_yx.png" class="bkxl_tp" ></image>
- </view>
- </view>
- </view>
- <view class="bkxl_mc">游戏开发</view>
- </view>
- <view style="height: 10px;"></view>
- </view>
- </block>
- </view>
- </block>
- </mp-tabs>
- </view>
- </view>

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
- <view class="container">
-
- <view class="game-body">
- <loading hidden="{{hidden}}">
- 加载中...
- </loading>
- <view class="heading">
- <text class="title">2048</text>
- <view class="scores-container">
- <view class="score-container">{{score}}</view>
- <view class="best-container">{{highscore}}</view>
- </view>
- </view>
-
- <view class="above-game" style="margin-bottom: 30px;">
- <text class="game-intro">你能拿到2048吗?</text>
- <text class="restart-button" bindtap="restart">新游戏</text>
- </view>
-
- <view class="game-container" style="margin: auto;">
- <view class="game-message game-{{over ? (win ? 'won' : 'over') : ''}}">
- <text class="over-msg">{{overMsg}}</text>
- <view class="lower">
- <!-- <text class="keep-playing-button">继续</text> -->
- <text class="retry-button" bindtap="restart">再试一次</text>
- </view>
- </view>
-
- <view class="grid-container" bindtouchstart="touchStart" bindtouchmove="touchMove" bindtouchend="touchEnd">
- <view wx:for="{{grids}}" wx:for-index="rowIdx" wx:for-item="row" class="grid-row">
- <view wx:for="{{row}}" wx:for-index="colIdx" wx:for-item="cell" class="grid-cell">
- <view class="tile tile-{{cell.value}}">
- <view wx:if="{{cell}}" class="tile-inner">
- {{cell.value}}
- </view>
- </view>
- </view>
- </view>
- </view>
- </view>
- </view>
- </view>

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
- <!-- 小游戏页面 -->
- <block wx:if="{{item.type === 'xyx'}}">
- <view wx:for-items="{{gameList}}" wx:key="*this" class="usermotto">
- <button class="game-2048" type="primary" disabled="{{disabled}}" bindtap="start{{item}}"> {{item}} </button>
- </view>
- </block>
案例效果:
因非认证的个人小程序的原因,关于我的页面也只能草草了事。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。