OneWord 是个人的练习之作,页面结构尽可能仿照ios端app 一言
Note: 开发期间该APP改版过几次,部分细节有变化
前端采用vue+vuex+vue-router,使用了部分PWA特性
后端采用koa2,数据库使用postgress,redis用于保存session
服务器部署在阿里云,采用nginx进行端口转发,又拍云做CDN加速,使用HTTPS协议
已失效
ios端如果在safari浏览器下访问,点击浏览器下方分享按钮,添加到主屏幕 Android端 也可以添加到桌面,具体方式请自行尝试
网站目前没有做PC端适配,建议在调试模式下测试效果
Note: 请尽可能使用移动端添加到桌面方式访问,可得到媲美原生APP的体验
| 所在路由或组件 | 触发条件 | 效果 |
|---|---|---|
| /login | 登录注册 | |
| /home | 点击右上角 | 进入/crosstie |
| 所有卡片 | 点击卡片顶部或中部 | 进行过度动画跳转到card-container |
| 所有卡片 | 点击卡片底部 | 点击评论图标跳转到/comment,点击喜欢图标喜欢,再次点击不喜欢(点击喜欢不喜欢之后跳转到/comment相应参数也会改变) |
| 音乐卡片 | 点击播放按钮 | 点击播放音乐,右下角显示转盘,点击转盘,跳转到音乐相关信息;点击暂停音乐,右下角转盘消失,音乐暂停;进入cardcontainer中对应的音乐卡片,可拖动进度条跳转音乐,暂停音乐;点击其他音乐,切换到其他音乐 |
| card-container | 左右滑动切换卡片,点击返回,回到上一个页面,并跳转到之前切换到的卡片, | |
| 查看新添加的卡片 | 左滑或者下拉刷新 | |
| /home /crosstime | 在卡片区域滑动 | 快速滑动 ,滑动越快,要滑动到的卡片位置越靠前或者越靠后,如果要滑动到的卡片位置超过了卡片最大位置索引,滑动到loading动画位置,更新数据;拖动,放手的时候如果拖动的距离大于1/4VW,滑动到上一张或者下一张,否则返回之前的一张卡片 |
| /choose-crosstime | 点击左侧月份,用户点击任意一天会跳转到相对应日期的所有卡片(目前数据太少未实现跳转对应日期的所有卡片,默认显示所有卡片)并且点击后localStorage会存储用户此次点击记录,点击的那个日期项会永久变成灰色,除非用户删除localStorage | |
| scroll-x scrol-y-cards | 滚动初始点向右滑动或者往下滑动 | 每次滑动从初始点滑动时动画项的初始位置都会打乱 ;快速滑动或者滑动到阈值会触发刷新;请求时间小于600ms,会延迟到600ms动画才会回弹,没有数据了会显示END |
| /commen | 点击底部输入框 | 发送评论,评论会发送成功会马上更新到评论界面,并且返回卡片界面,评论数也会更新,不会再次请求数据 |
| /commen | 点击别人评论 | 弹出drawer,可复制,回复别人评论,点击右侧图标可点赞别人评论 |
| /commen | 点击自己的评论 | 弹出drawer,可复制,删除评论,点击右侧图标会提示不能点赞自己的评论 |
| /explore/all | 左右滑动 | 顶部下方动画会跟随滑动滑动 |
| /mine | 点击头像 | 底部背景模糊,编辑用户资料,点击头像图片,选择图片,选择图片裁切区域,裁切图片,点击完成,上传修改 |
| /mine | 点击退出登录 | 退出登录,清除cookie,重置vuex为初始值 |
| /mine | 点击新建文集 | 弹出新建文集页面,点击文集封面图,选择图片,选择图片裁切区域,裁切图片,点击完成,上传保存 |
| /new-word | 发布图文 | 正文为必填项,私密下方文字会根据是否勾选私密切换内容,选择分类为文字归类,点击添加图标,选择文字图片; |
| /new-word | 选择存入文集 | 选择存入文集,也可以新建文集 |
| /new-word | 选择样式 | 点击第一排图标,切换横向排列文字或者竖向排列,竖向排列时,无法点击最后一排的第三个切换对齐方式图标,此时只有默认对齐方式;点击最后一排图标,第一个图标,切换图片圆形或者矩形铺满,点击切换后,会有过渡动画,点击第二个图标,切换字体,一共有五种字体可供选择,点击第三个图标,切换文字对齐方式,左对齐或者居中 |
应用内截图,通过设计软件测量各区域的长宽颜色等各种值
应用内录屏,逐帧查看测量各区域的长宽颜色等各种值
mock数据 通过用fiddler4抓包
pwa在项目中是为了让应用全屏化 ,通过workbox将字体文件设置为采用缓存优先策略
通过 https://www.likefont.com/ 找到相似字体
通过 https://transfonter.org/formats 将字体转换为Base64格式
网站加载后 将其挂载在head标签
base64格式字体文件在ios端中英文中线无法居中,遂直接采用ttf格式字体文件,使用FontFace APi异步加载字体文件
www.iconfont.cn 使用iconfont图标
宽高采用vw,使用stylus-px-to-relative-unit包,配置后可直接设置为测量值,stylus-px-to-relative-unit会将其自动转换为vw值,布局大部分采用flex布局,少部分采用grid布局
better-scroll animatejs axios等
所有ui组件均自行设计,只要是需要用到两次及以上的部分都会拆分为组件,下面分析重要组件
card组件是应用的核心,分为两大类,根据卡片类型将其归类为card-music和card-text , 对应音乐卡片和文字卡片,两种卡片有很多相似点,将其中相似区域拆分为card-base组件,通过props传入不同的值展示不同的内容
音乐卡片有两种,根据卡片类型将其归类为card-fixed-music、card-detail-music对应固定音乐卡片、细节音乐卡片,音乐卡片,除了使用部分card-base基础组件还使用了music-player、music-player-control、music-process-bar三个其特有的组件
music-player组件会监听vuex中musicSrc、currentTime、playing ,对应音乐路径,当前播放时间,音乐播放状态,当musicSrc改变 切歌;当currentTime改变,说明用户在拖动进度条跳转到拖动的音乐进度;当playing状态改变,播放或者暂停歌曲
music-player-control组件,监听vuex中的musicSrc,如果musicSrc和当前控制器记录的musicSrc一致,切换播放暂停状态,点击该组件,会commit 播放暂停状态到vuex
music-process-bar 组件,监听vuex中的musicSrc,如果musciSrc和当前进度条记录的musicSrc一致,进度条会监听vuex中的currentTime,如果之前的musicSrc和当前进度条记录的musicSrc一致,说明现在切掉了这首歌,取消之前的监听事件,初始化一些状态
文字卡片有三种,根据卡片类型将其归类为card-flex-text、card-fixed-text、card-detail-text 对应自适应卡片、固定文字卡片、细节文字卡片,除了使用部分card-base基础组件还使用了card-base-content-horizontal、card-base-content-vertical两个其特有的组件,用于文字水平布局和垂直布局
该组件用于点击卡片后执行一个过渡动画,切换到card-detail,左右滑动可以切换目前加载到的所有卡片
由于使用了betterscroll ,该库通过transfrom模拟原生APP内部的滚动滑动效果,但是transform 会形成一个新的上下文环境,脱离了文档流,动画需要逐渐隐藏所有其他元素,将card元素前置并移动到中部,然后再显示card-detail,需要设置z-index,由于zindex的层级是只针对当前文档流的,也就是设置index,不会影响transform之外的文档流,那么肯定不能在transform的文档流中运行动画,这里一般思路是将其放置在根组件同级位置,每次点击通过vuex传递要移动的卡片信息,生成新的卡片来执行动画,这里我采用获取transform坐标然后移除tranform,使用获取到的坐标通过position定位,动画执行完毕再将其设置成以前的transform坐标
穿越组件用于穿越页面,拆分为两部分,左侧为crosstime-month,右侧为crosstime-date,crosstime-month中的月份项点击后会携带月份信息触发monthChange事件,crosstime-date可以通过props接收到月份信息,根据信息改变crosstime-date组件的日期信息,展示这个月的每一天,用户点击任意一天会跳转到相对应日期的所有卡片(目前数据太少未实现跳转对应日期的所有卡片,默认显示所有卡片)并且点击后localStorage会存储用户此次点击记录,点击的那个日期项会永久变成灰色,除非用户删除localStorage
该组件基于vue-cropper组件
scrollbase组件基于better-scroll,绝大多数页面都使用了该组件
scroll-x和scroll-y-cards将card和其他元素高度封装到了一起,因为多个页面会用到,使用是只需要传入相关参数如下,通用性和实用性非常高
<scroll-y-cards
ref="scroll"
:pullUp="true"
@pullUp="handlePullUp"
:pullDown="true"
@pullDown="handlePullDown"
/>scroll-y 较之 scroll-y-cards 只是没有嵌入card相关组件,更具用通用性。
scroll-x、scroll-y均有loading动画,滚动到一定值会触发刷新事件,根据观察原APP,每次下拉或者右滑动组件loading动画项的起始位置都不一样,但结束位置都是一样的,这里用一个数组来记录每个动画项需要移动到的位置参数
// 用来记录每一条动画中的li的长度,需要偏转的角度,位置,x轴偏移量,y轴偏移量
list: [
[LONG_LINE_HEIGHT, 90, 0, -22, 0],
[LONG_LINE_HEIGHT, 50, 1, -10, 0],
[LONG_LINE_HEIGHT, 90, 2, 2, 0],
[LONG_LINE_HEIGHT, 65, 3, 5, 0],
[SHORT_LINE_HEIGHT, 0, 4, 20, 0],
[LONG_LINE_HEIGHT, 115, 5, 35, 0],
[SHORT_LINE_HEIGHT, 135, 6, 40, -5],
[SHORT_LINE_HEIGHT, 90, 7, 55, 4],
[SHORT_LINE_HEIGHT, 45, 8, 70, -5],
[LONG_LINE_HEIGHT, 90, 9, 30, 0],
[SHORT_LINE_HEIGHT, 145, 10, 25, -5],
[SHORT_LINE_HEIGHT, 90, 11, 40, 4],
[SHORT_LINE_HEIGHT, 45, 12, 55, -5]
]监听滚动事件,每次滚动的值大于0,就去打乱list,更新dom之后,拿到dom并缓存起来,如果已经缓存起来了就不打乱更新dom了,滚动结束就清空缓存的dom。滚动过程中根据滚动的值实时变换动画
scroll-y中loading动画在请求到数据后再结束,如果单单实现这个一个效果是很简单的,直接使用bs 的pulldown库,基本满足需求,但必须要达到阈值才能触发,原APP当执行快速下拉时也会触发,查看pulldown源码下拉相关代码
if (!this.pulling) {
this.pulling = true
this.scroll.trigger('pullingDown')
this.originalMinScrollY = this.scroll.minScrollY
this.scroll.minScrollY = stop
}使用了minScrollY 设置了下拉的停顿坐标,并触发了下拉事件,参考其定义了一个手动触发函数,快速下拉时执行该函数
scroll-x中滚动开始前保存开始时间和当前滚动的位置,滚动结束时计算这次滚动用时和滚动距离,用时小于200ms滚动距离大于20,就会触发快速滑动卡片,卡片会根据滚动距离的多少滚动n张,其他情况也就是慢速滚动的时候卡片跟随手指移动,手指松开时,获取当前卡片距离左右两侧的距离,根据距离判断该滑动到左侧或者右侧
通过闭包封装axios,封装成基础的两个post、get方法,用于post、get请求,如果请求过程中出错会弹出toast报错
拦截axios请求,如果此时是需要显示loading的请求显示请求动画,响应后关闭请求动画
使用了modules,将数据分为3类,card、common、music,分别对应卡片信息,公用信息、音乐信息。
定义了mutation-types.js 文件来存储mutation名称常量
其中card中有10种卡片数据,对应10个页面,为评论页面创建了增加评论数mutation,获取卡片信息的getter,为每个card页面创建了设置卡片信息的mutation
RESET_CARD_STATE mutation 用于用户退出登陆之后重置mutation为初始值
路由元信息上绑定index 用来判定是进入还是退出,执行相对应的动画
根据路由路径动态设置keep-alive组件的exclude props
路由模式 history 路由守卫 登录鉴权
实现toast
由于有多种卡片且卡片都相似方法,将其抽离出来作为mixin
css中设置 -webkit-tap-highlight-color: transparent 移除IOS端点击后高亮效果 css中设置 touch-action: manipulation;解决点击300ms问题
env.js
passport+koa-redis+koa-session
catchError.js 用于捕获异常 changeStaticPath.js 用于修改请求的静态目录路径 decode.js 用于解码请求 isLogin.js 用于判断用户是否登录,绝大多数API需要登录才能访问 normalBody.js 用于在ctx上添加相应方法
使用sequelize 操作 postgress数据库
/onewordback/dbs/models 目录中book.js card.js comment.js user.js 中分别定义了书籍表、卡片表、评论表、用户表并在associtate.js中设置了关联
nginx 转发80端口到443 /yiyan/ 路径的请求代理到pm2管理的本地3000端口服务上 /js/png/mp3 缓存过期时间为30天
nginx 部分配置文件如下
location / {
if ($request_filename ~* .*\.(?:htm|html)$)
{
add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
}
if ($request_filename ~* .*\.(?:js|css|png)$)
{
expires 30d;
}
root /root/oneword/;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location /yiyan/ {
if ($request_filename ~* .*\.(?:jpg|mp3)$)
{
expires 30d;
}
if ($request_filename ~* .*\.(?:ttf)$)
{
expires 365d;
}
proxy_pass http://localhost:3000;
}又拍云使用默认设置