旅梦飞起

旅梦飞起 查看完整档案

南昌编辑  |  填写毕业院校  |  填写所在公司/组织 coderysw.leanapp.cn 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

旅梦飞起 赞了文章 · 2019-10-06

Nuxt 实现用户鉴权登陆

引言

博客前台重构完毕了,接下来就是后台部分了,后台的主要功能就是发布、删除、修改文章,自然不是谁都能随便进的。在 vue 项目中,我是在 Vue Router 的全局前置守卫里判断当前用户是否有 cookie 从而判断是否有权进入后台。而 Nuxt 相比 Vue 项目最大的不同之一就是没有使用 Vue Router 而是使用 目录来进行页面路由,自然我们就失去了 全局前置守卫这个利器,当然 Nuxt 是有解决办的,不过在那之前我们需要先来了解一下鉴权的原理。

Cookie 和 Session

相信前端的同学们对这两个名字早就有所耳闻,却不一定有详细的了解。众所周知,我们浏览网页使用的 HTTP 协议是无状态的的,也就是说你每一次请求对于服务器来说都是一样的,它没有办法记住这个请求是你发的。所以这里就要用到Cookie

Cookie 是服务端设置的,由浏览器储存在你的硬盘中的一组数据,,比如你的用户 数据,每次向服务器发送请求就会携带上这个数据。服务器查看就能知道这是谁发过来的。这一过程就称为Session(会话)

Session 初始是指一种概念,是你和网站发生交互的一个周期。在这个周期中服务器就是通过储存在浏览器的 Cookie 来判别你是谁。但是因为储存在本地的Cookie并不安全,谁都可以看到并更改,所以现在更为流行的做法是仅仅通过 Cookie 保存 的唯一的用户标识符(SessionID)来识别用户,而用户信息储存在服务器端。所以 Session 这个概念可以说是 Cookie 的上级也可以说是其同级

Nuxt 鉴权

讲解了 Nuxt 鉴权的基本原理,我们可以知道鉴权就是在在用户进入这个页面的时候对本地的 Cookie 进行判断,存在设置好的 Cookie 那么说明这个用户已经登陆过了,放他过去。啥也没有? 不行你去给我登陆,就跳转到登录页面。明白了这个流程就开始具体的工作了。

服务器端

在服务器端我们使用 koa-session 安装 koa-session

npm install koa-session 
npm install koa-session-mongoose //使用 mongodb 储存 Session 信息

然后在入口文件中这样使用

app.use(
  session(
    {
      key: "***",         //加密密钥
      overwrite: true,    //覆写Cookie
      httpOnly: true,     //经允许通过 JS 来更改
      renew: true,
      store: new MongooseStore({
        createIndexes: "appSessions",
        connection: mongoose,
        expires: 86400, // 1 day is the default
        name: "AppSession"
      })                  //传入一个用于session的外部储存,我这里是使用了 mongodb 
    },
    app
  )
);

因为 koa 默认会把 Session 打到 ctx.session 中,不方便用户端获取,所以我们把它移一下位,挪到 ctx.req.session

  app.use((ctx) => {
    ctx.status = 200
    ctx.respond = false // Bypass Koa's built-in response handling
    ctx.req.session = ctx.session
    ctx.req.ctx = ctx // This might be useful later on, e.g. in nuxtServerInit or with nuxt-stash
      return new Promise((resolve, reject) => {
        ctx.res.on('close', resolve)
        ctx.res.on('finish', resolve)
        nuxt.render(ctx.req, ctx.res, promise => {
          // nuxt.render passes a rejected promise into callback on error.
          promise.then(resolve).catch(reject)
        })
      })
  })

这是登陆函数,查询数据库是否又对应的用户名和密码,存在的话,给客户端设置一个 Cookie 返回登录成功

  static async login(ctx) {
    let { passwd, email } = ctx.request.body;
    let hasuser = await UserModel.findOne({ email: email, passwd: md(passwd) });

    if (!hasuser) {
      return ctx.error({});
    }
    else {
      let userid = hasuser._id;
      const { session } = ctx;
      session.userid = userid;
      return ctx.success({ data: { userid: userid } });
    }
  }

服务端设置完成了

客户端(Nuxt)

其实以上的步骤和 Vue 项目中一模一样,Nuxt 中主要的不同就是失去了全局前置守卫,那么要在哪里判断是否存在 Cookie 呢,别急,Nuxt 官方自然是给了解决方案,先看一下 Nuxt 的生命周期

image

这里我们用到的就是红框中的 nuxtServerInitmiddleware 这两个时期,先来看代码

// store/index.js  Vuex 文件中
export const actions = {
  // nuxtServerInit is called by Nuxt.js before server-rendering every page
  nuxtServerInit({ commit }, { req }) {
    if (req.session && req.session.userid) {
      console.log("用户已经登录");
      commit("SET_USER", req.session.userid);
    }
  },
  
  export const mutations = {
  SET_USER(state, user) {
    state.authUser = user;
  },
  }

Store action 模块中的 nuxtServerInit 函数是整个生命周期 最先运行的,我们就在这里判断当前用户浏览器中是否有 Cookie ,如果有的话就在 state 中用一个字段保存下来。是不是还挺像全局前置守卫。这里还只是做了判断,打上了印记你登没登陆,拦截在哪里呢,别急,就是下一个流程 middleware 中。

打开 middleware 文件夹( Nuxt 项目自带),新建 auth.js 文件

// auth.js
export default function ({ store, redirect }) {
  if (!store.state.authUser) {
    return redirect('/welcome')
  }
}

瞧一下 Vuex 中看看你有没有登陆,没有的话把你送到登陆页面去,简单直接吧,只要在需要鉴权的页面引用这个中间件即可,对于此项目只要在后台管理页面引用就好

export default {
    middleware: 'auth',
};

总结

就这样完成了鉴权的操作,没有登陆过的用户在访问后台是时候会被重定向到登陆页面去,就是很简单的使用了一下 Cookie ,限于项目性质,session 的很多功能并没有用到,比如在服务器端储存用户信息。主要是它的功能也就是防止别人访问后台,非常简单。

还会有后续文章

  • koa-session 源码分析
  • 使用 koa-session 将用户信息储存在 mongodb 中

敬请期待


Welcome to my Blog

查看原文

赞 6 收藏 4 评论 0

旅梦飞起 赞了文章 · 2019-10-06

( 第五篇 )仿写'Vue生态'系列___"解析模板事件"

( 第五篇 )仿写'Vue生态'系列___"解析模板事件"


本次任务

  1. 取消'eval', 改为'new Function'.
  2. 支持用户使用'@'与'v-on'绑定各种事件.
  3. 支持初始化'methods'数据.
  4. 使用函数时可以传参与不传参, 可以使用'$event'.
  5. 实现'c-center'与'c-show'指令.
  6. 实现'cc_cb'函数, 模板里面也可以用if - else.

一. eval 与 Function

项目里面的取值操作, 我之前一直采用的都是eval函数, 但是前段时间突然发现一个特别棒的函数Function, 下面我来演示一下他的神奇之处.

1. 可以执行字符串

 let fn1 = new Function('var a = 1;return a');
 console.log(fn1()); // 1

2. 可以传递参数
下面写的name与age就是传入函数的两个参数,

let fn2 = new Function('name','age', ' return name+age');
console.log(fn2('lulu',24)); // lulu24

第二种传参方式

let fn3 = new Function('name, age', ' return name+age');
console.log(fn3('lulu',24)); // lulu24

综上我可以推断, 他的原理是把最后一个参数当做执行体, 然后前面如果有参数就被当做新生成函数的参数.

3. 全局作用域
他执行的时候里面的作用域是全局的, 就算在函数内部, 执行时候也取不到函数内部的值, 所以想要使用的值, 都需要我们手动传进去.

// 报错了, 找不到u
function cc(){
    let u = 777;
    let fn = new Function('var a = 5;console.log(u); return a');
    console.log(fn());
  }
 cc()
// 执行成功
function cc(){
    u = 777; // 直接挂在window上
    let fn = new Function('var a = 5;console.log(u); return a'); // 777
    console.log(fn()); // 5
  }
 cc()

我也试了一下, 里面的var a 并不会污染全局, 放心使用吧;

把它介绍清楚了, 我就可以用它来替换之前写的eval了
expression: 表达式, 例如 'obj[name].age'

getVal(vm, expression) {
    let result, __whoToVar = '';
    for (let i in vm.$data) {
      __whoToVar += `let ${i} = vm['${i}'];`;
    }
      __whoToVar = `${__whoToVar} return ${expression}`;
    result = new Function('vm', __whoToVar)(vm);
    return result;
  },

这里以后还会改成一个公用的获取变量的'池', 应该会下一章去做.

二. '@'与'v-on'

所谓指令当然是要绑定在元素的身上, 我们有一个compileElement方法来处理元素节点, 那么正好利用他来让我们分出一个指令处理模块.
比如说指令, 本次我们来做v-show指令.
事件的话就是所有的原生事件.

compileElement(node) {
    let attributes = node.attributes;
    [...attributes].map(attr => {
      let name = attr.name,
        value = attr.value,
        obj = this.isDirective(name);
      if (obj.type === '指令') {
        CompileUtil.dir[obj.attrName] &&
          CompileUtil.dir[obj.attrName](
            this.vm,
            node,
            CompileUtil.getVal(this.vm, value),
            value
          );
      } else if (obj.type === '事件') {
        // 当前只处理了原生事件;
        if(CompileUtil.eventHandler.list.includes(obj.attrName)){
         CompileUtil.eventHandler.handler(obj.attrName,this.vm, node, value);
        }else{
          // eventHandler[obj.attrName] 这个事件不是原生挂载事件, 不能用handler 处理
        }
      }
    });
  }

上面有一个isDirective事件, 这个事件也是一个关键点.
我们现在分成四种形式.
判断出类型, 切分出后面的指令名称与参数, 返回给处理程序.

  isDirective(attrName) {
    if (attrName.startsWith('c-')) {
      return { type: '指令', attrName: attrName.split('c-')[1] };
    } else if (attrName.startsWith(':')) {
      return { type: '变量', attrName: attrName.split(':')[1] };
    } else if (attrName.startsWith('v-on:')) {
      return { type: '事件', attrName: attrName.split('v-on:')[1] };
    } else if (attrName.startsWith('@')) {
      return { type: '事件', attrName: attrName.split('@')[1] };
    }
    return {};
  }

cc_vue/src/CompileUtil.js
这里面专门抽出一个指令处理模块, 暂命名为dir.
本次就以 c-html 与 c-show 为例
c-html 顾名思义, 就是用户传一段html代码, 然后我把它注入到dom结构中

dir: {
    html(vm, node, value, expr) {
    // 只有这样一个操作就可以了, 没有任何高深的东西
      node.innerHTML = value;
      // 这里别忘了用watcher订阅一下变化, 达到双向绑定的效果.
      new Watcher(vm, expr, (old, newVale) => {
        node.innerHTML = newVale;
      });
    }
  },

热身之后剩下的这个'c-center'与'c-show'就非常有趣了

  1. 控制'dom'的'display:none'属性, 为'true'的时候显示 , 为'false'的时候'dom'要消失.
  2. 这个属性不可以影响dom本身的行间样式, 比如用户定义的就是'none', 当他为'true'的时候依然不可以显示'dom'元素.
  3. 这个属性不可以改变dom本身的任何属性, 但是优先级还要最高, 脑子里一瞬间出现的竟然是'!important'.

综上分析得出两种方案:
第一种: 把所有外在因素全部考虑进来, 每次进行整体分析, 得出具体的结论到底是'block'还是'none' 也可能是 'flex' 与 'grid' 等等的.
第二种: 本次我想另辟蹊径的方法, 动态插入'css'代码, 这个想法挺有意思吧, 框架执行时, 先插入一段css代码, 然后可以利用这个css做很多很多有趣的事, 这方面以后会有扩展.
独立出一个插入'css'代码的模块.
单独new一下
cc_vue/src/index.js

import CCStyle from './CCStyle.js';
class C {
  constructor(options) {
     for (let key in options) {
      this['$' + key] = options[key];
    }
    new CCStyle();
    // ...

cc_vue/src/CCStyle.js

class CCStyle {
  constructor() {
     // 我要把它插到最上, js里面没有插到第一个位置这样的语句, 我只能获取到第一个元素, 然后插在他的前面.
    let first = document.body.firstChild,
        style = document.createElement('style'); // 当然是做一个style标签.
    // 这里先定一个c-show的绝对隐藏属性.
    style.innerText='.cc_vue-hidden{display:noneimportant}';
    // 放进去就生效了, 以后控制v-show就只需要为元素添加与移除这个class名字就可以了.
    document.body.insertBefore(style, first);
  }
}

export default CCStyle;

上面的代码明显不符合设计模式, 我们来把它的'可扩展性'优化一下.

class CCStyle {
  constructor() {
    let first = document.body.firstChild,
      style = document.createElement('style'),
      typeList = this.typeList();
    // 不管具体的属性是什么, 我们只管在这里面循环出来, 然后拼接上去,这里我们自己压缩一下他.
    for (let key in typeList) {
       style.innerText += `.${key}{${typeList[key]}}\n`;
    }
    document.body.insertBefore(style, first);
  }
// 这里面我们可以分门别类的扩展很多属性.
  typeList() {
     return {
      // 1: 控制元素隐藏的
      'cc_vue-hidden': 'display:none!important'

      // 2: 控制元素上下左右居中的
      'cc_vue-center':'display: flex;justify-content: center;align-items: center;'
    };
  }
}

export default CCStyle;

v-center 指令
cc_vue/src/CompileUtil.js

center(vm, node, value, expr) {
      value
        ? node.classList.remove('cc_vue-center')
        : node.classList.add('cc_vue-center');
      new Watcher(vm, expr, (old, newVale) => {
        newVale
          ? node.classList.remove('cc_vue-center')
          : node.classList.add('cc_vue-center');
      });
    }

c-show的原理与上面是一样的

show(vm, node, value, expr) {
      value
        ? node.classList.remove('cc_vue-hidden')
        : node.classList.add('cc_vue-hidden');
      new Watcher(vm, expr, (old, newVale) => {
        newVale
          ? node.classList.remove('cc_vue-hidden')
          : node.classList.add('cc_vue-hidden');
      });
    },

三. methods 与 事件的绑定

methods 晚于 data定义, 在用户出现重复定义的时候, 要给一个友好的提示.
cc_vue/src/index.js

class C {
  constructor(options) {
    // ...
    // proxyVm $data之后来处理$methods
    this.proxyVm(this.$methods, this, true);

绑定函数要稍作改变, 只要不传target 就是与vm实例绑定, noRepeat是否检测重复数据, 也就是报不报错.

 proxyVm(data = {}, target = this, noRepeat = false) {
    for (let key in data) {
      if (noRepeat && target[key]) { // 防止data里面的变量名与其他属性重复
        throw Error(`变量名${key}重复`);
      }
      Reflect.defineProperty(target, key, {
        enumerable: true, // 描述属性是否会出现在for in 或者 Object.keys()的遍历中
        configurable: true, // 描述属性是否配置,以及可否删除
        get() {
          return Reflect.get(data, key);
        },
        set(newVal) {
          if (newVal !== data[key]) {
            Reflect.set(data, key, newVal);
          }
        }
      });
    }
  }

处理好methods的数据了, 就要处理事件的绑定了.
分配的逻辑之前已经展示过了

// 如果事件列表里面有这个事件, 那么就绑定这个事件.
if(CompileUtil.eventHandler.list.includes(obj.attrName)){
   CompileUtil.eventHandler.handler(obj.attrName,this.vm, node, value);
}

cc_vue/src/CompileUtil.js
专门处理事件的模块

  eventHandler: {
     // 这个选项用来维护可处理的原生事件, 下面只是举例并不全面.
    list: [
      'click',
      'mousemove',
      'dblClick',
      'mousedown',
      'mouseup',
      'blur',
      'focus'
    ],
    // 确定含有事件时进行的操作
    handler(eventName, vm, node, type) {
      // ...
     }
    }
  }

handler要解决的问题形式

  1. add ---> 直接调取.
  2. add() ---> 括号调取.
  3. add( ) ---> 夹杂空格.
  4. add(n, m, 9) ---> 夹杂空格,常量,变量的传参.
  5. add(n, $event) ---> 用户想要获取事件对象$event.

那我们就来分步处理这几种情况吧.

 handler(eventName, vm, node, type) {
    // 第一步: 匹配一个是否含有'()';
      if (/\(.*\)/.test(type)) {
        // 第二步: 把'()'里面的内容拿出来
        let str = /\((.*)\)/.exec(type)[1];
        // 去除空格
        str = str.replace(/\s/g, '');
        // 以"("分割, 取到事件名字
        type = type.split('(')[0];
        // '()'里面有内容才进行这一步;
        if (str) {
        // 第三步: 参数化'组'
          let arg = str.split(',');
          // 第四部: 绑定事件与解析参数
          node.addEventListener(
            eventName,
            e => {
            // 循环这个参数组
              for (let i = 0; i < arg.length; i++) {
                // 这样就做到了$event的映射关系
                arg[i] === '$event' && (arg[i] = e);
              }
              vm[type].apply(vm, arg);
            },
            false
          );
          return;
        }
      }
      // 第二步: 不带括号的直接挂就行了
      node.addEventListener(
        eventName,
        () => {
          vm[type].call(vm); // this肯定指向vm, 毕竟用户要使用$data等等属性
        },
        false
      );
    }

上面没有对参数为$data上的变量的情况时做处理, 因为没有太大的必要, 以后写到 c-for的时候, 会着重的改写一下这边的逻辑.

四. 在模板内使用if

我们使用vue开发的时候, 只允许在模板中使用表达式, 这次我玩的这个项目, 允许用户使用任何形式去写, 当然了这样有一些性能之类的弊端, 但是为了好玩, 什么我都愿意尝试, 摒弃了return出值的写法, 采取了callback的模式.
关键字 cc_cb(value) value就是要传出来的值.
用法如下:

<div>
{{ 
  if(n > 3){
    cc_cb(n) 
  }else{
    cc_cb('n小于等于3')
  };
}}
</div>

其实这种功能并不复杂, 只是书写起来挺讨厌的, 而且太太太违背设计模式了.
只需要改变getVal函数

  getVal(vm, expression) {
    let result,
      __whoToVar = '';
    for (let i in vm.$data) {
      __whoToVar += `let ${i} = vm['${i}'];`;
    }
    // 检测到存在cc_cb被调用的情况时
    if (/cc_cb/.test(expression)) {
         // 无非就是把返回的值, return出来
      __whoToVar = `let _res;function cc_cb(v){ _res = v;}${__whoToVar}${expression};return _res`;
    } else {
      __whoToVar = `${__whoToVar} return ${expression}`;
    }
    result = new Function('vm', __whoToVar)(vm);
    return result;
  },

嘿嘿仅需小小的改动, 就做到了这么神奇的事情.

end

这个框架刚刚做了一点点就已经出现很多性能问题了, 接下来我会针对取值问题进行一次深层次的优化, 想想还挺兴奋.

下一集:

  1. 优化取值.
  2. 添加hook生命周期钩子.

github:链接描述
个人技术博客:链接描述
更多文章,ui库的编写文章列表 :链接描述

查看原文

赞 11 收藏 9 评论 4

旅梦飞起 赞了文章 · 2019-09-14

借助实时数据推送快速制作在线对战五子棋小游戏丨实战

1 项目概述

游戏开发,尤其是微信小游戏开发,是最近几年比较热门的话题。

本次「云开发」公开课,将通过实战「在线对战五子棋」,一步步带领大家,在不借助后端的情况下,利用「小程序 ✖ 云开发」,独立完成一款微信小游戏的开发与上线。

2 任务目标

根据项目初始框架,阅读教程的同时,逐步完成棋盘绘制、音乐播放、玩家对战、输赢判定等功能,最终实现一个可以快乐玩耍的在线对战五子棋。

在这个过程中,会了解到 Serverless 的一些概念,并且实际应用它们,比如:云数据库云存储云函数增值能力。除了这些基本功能,还准备了更多的硬核概念与落地实践,比如:实时数据库聚合搜索权限控制

完成开发后,上传并且设置为体验版,欢迎邀请更多人来体验。

3 准备工作

TencentCloudBase/tcb-game-gomoku 中下载代码到本地:

git clone https://github.com/TencentCloudBase/tcb-game-gomoku.git
cd tcb-game-gomoku/

切换课程专用的 minigame-study 分支:

git checkout minigame-study

4 实战任务

4.1 创建云开发与小游戏环境

1、打开微信 IDE,点击左侧的小游戏,选择右侧的导入项目,导入之前下载的「在线对战五子棋」的目录,AppID 修改为你已经注册好的小游戏 AppID。

2、进入后,点击上方的云开发按钮。如果之前没有开通过云开发,需要开通云开发,新开通的话需要等待 10 ~ 20 分钟。

3、进入「云开发/数据库」,创建新的集合,新集合的名称是rooms

4、进入「云开发/存储」,点击“上传文件”。上传的内容是/static/下的bgm.mp3fall.mp3。之后的代码中会通过云存储的接口,请求文件的临时 url,这样做的目的是减少用户首次进入游戏加载的静态资源

4.2 准备配置文件

创建配置文件:

cp miniprogram/shared/config.example.js miniprogram/shared/config.js

将关键字段的信息,换成自己账号的信息即可:

4.3 创建云开发接口

打开 miniprogram/shared/cloud.js,在里面初始化云开发能力,并且对外暴露云数据库以及聚合搜索的 API。

4.4 获取云存储资源的链接

为了减少用户首屏加载的静态资源,音乐资源并没有放在miniprogram目录下,而是放在了云存储中,通过调用云存储的 api 接口,来返回静态资源的临时链接。

miniprogram/modules/music.js中,会调用资源接口,获取资源链接:

getTempFileURL函数属于云开发相关,因此放在了 miniprogram/shared/cloud.js中。这里只需要临时链接tempFileURL属性,其它返回值直接过滤调即可。

为了方便外面调用,promise 内部不再用 reject 抛错。对于错误异常,返回空字符串。这样,加载失败的资源不会影响正常资源的加载和 Promise.all 中逻辑进行。

4.5 游戏进入与身份判断

根据前面的流程图我们可以看到,游戏玩家的身份是分为 owner 与 player。它们的含义如下:

  • owner:玩家进入游戏后,查找是否有空闲房间,如果不存在空闲房间,那么就会主动创建新的空闲房间。那么对于新创建的房间,玩家就是 owner。
  • player:玩家进入游戏后,查找是否有空闲房间,如果存在空闲房间,那么就加入空闲房间。那么对于空闲房间,玩家就是 player。

判断的依据就是 judgeIdentity 方法中,读取云数据库集合中的 rooms 的记录。如果存在多个空闲房间,需要选取创建时间最近的一个房间。因此,这里需要用到「聚合搜索」的逻辑。

聚合搜索的条件,在这里有 3 个:

  1. 标记人数的字段,是否为 1
  2. 创建时间倒叙排序
  3. 只选择 1 个

4.6 创建新房间

在上述的身份判断函数逻辑中,如果聚合搜索查询的结果为空,说明没有空闲房间,玩家需要作为 owner 来创建新的房间,等待其它玩家加入。

创建房间的逻辑就是将约定好的字段,放进云数据库的记录中。这些字段有:

  • roomid<String>: 6 位房间号,唯一
  • nextcolor<"white" | "black">: 下一步是白棋/黑棋走
  • chessmen<String>: 编码后的棋盘数据
  • createTimestamp<String>: 记录创建时间戳,精确到 ms
  • people<Number>: 房间人数

是的,你可能注意到了,这里需要保证 roomid 是不重复的。因此本地生成的随机 roomid,需要先调用云数据库的查询接口,检测是否存在。如果存在,那么递归调用,重新生成随机字符串。

4.7 监听玩家进入

对于 owner 身份来说,除了要创建新房间,还需要在创建后监听 player 身份的玩家进入游戏。

对于 player 身份的玩家进入游戏后,会更新记录中的 people 字段(1 => 2)。这时候就需要利用「实时数据库」的功能,监听远程记录的 people 字段变化。

代码实现上,调用watch方法,并且传递onChange函数参数。一旦有任何风吹草动,都可以在onChange回调函数中获得。对于传递给回调函数的参数,有两个比较重要:

  • docChanges<Array>: 数组中的每一项对应每条记录的变化类型,变化类型有 init、update、delete 等。
  • docs<Array>: 数组中的每一项对应每条记录的当前数据。

4.8 越权更新字段

对于 player 身份来说,进入房间后,既不需要「创建新房间」,也不需要「监听玩家进入」。但需要更新记录的 people 字段。由于记录是由 owner 身份的玩家创建的,而云数据库只有以下 4 种权限:

  • 所有用户可读,仅创建者可读写
  • 仅创建者可读写
  • 所有用户可读
  • 所有用户不可读写

以上 4 种权限,并没有「所有用户可读写」。因此,对于越权读写的情况,需要通过调用云函数来以“管理员”的权限实现。在 cloudfunction 中创建 updateDoc 云函数,接收前端传来的 collection、docid、data 字段。对于 data 字段来说,就是数据记录的最新更新数据。

在小游戏中,通过wx.cloud.callFunction来调用云函数。传入的 data 字段指明被调用的云函数,传入的 data 字段可以在云函数的回调函数的 event 参数中访问到(如上图所示)。

4.9 落子更新逻辑

不论对于 player 还是 owner 身份,都需要处理落子的逻辑。落子逻辑中,下面的两种情况是属于无效落子:

  1. 点击位置已经有棋子
  2. 对方还未落子,目前依然处于等待情况

对于以上两种情况,处理的逻辑分别是:

  1. 棋盘状态保存在内部类中,调用落子的函数,会返回是否成功的字段标识
  2. 只有监听到远程棋盘更新后,才会打开本地的锁,允许落子;落子后,会重新上锁

落子成功后,要在本地判断是否胜利。如果胜利,需要调用退出的逻辑。但无论是否胜利,都要将本地的最新状态更新到云端。

4.10 监听远程棋盘更新

不论对于 player 还是 owner 身份的玩家,都需要监听远程棋盘的更新逻辑。当远程棋盘字段更新时,本地根据最新的棋盘状态,重绘整个棋盘。并且进行输赢判定,如果可以判定输赢,则退出游戏;否则,打开本地的锁,玩家可以落子。

因为不同身份均需要监听,因此这一块的监听逻辑可以复用不同的是,两种身份的监听启动时间不一样。owner 身份需要等待 player 身份玩家进入游戏后才开启棋盘监听;player 身份是更新了 people 字段后,开启棋盘监听。

在监听逻辑中,需要判断远程更新的字段是否是 chessmen,这是通过前面提及的 dataType 来实现的。还徐哟啊判断记录中的 nextcolor 字段是否和本地的 color 一样,来决定是否打开本地的锁。

如果上述的两个条件均满足,则执行更新本地棋盘、判定输赢、打开本地锁的逻辑。

4.11 游戏结束与退出

每次需要判定输赢的地方,如果可以判定输赢,那么都会走到游戏退出逻辑。退出的逻辑分为 2 个部分,第 1 个是给用户提示,第 2 个是调用云函数清空记录。

第 1 个逻辑中用户提示,需要判定用户胜负状态:

第 2 个逻辑中清除记录的原因是为了方便调试,对于真正的业务场景,一般不会删除历史数据,方便问题定位。同时,这也是一个越权操作,需要调用云函数来实现。

6. 课程完整源码

https://github.com/TencentCloudBase/tcb-game-gomoku

7. 联系我们

更多云开发使用技巧及 Serverless 行业动态,扫码关注我们~

图片描述

查看原文

赞 8 收藏 5 评论 1

旅梦飞起 发布了文章 · 2019-08-30

看视频 轻松搞定 高频 JavaScript 手写面试题

手写面试题

大厂面试中最喜欢考手写代码,不仅可以看到个人的编程习惯,还能层层递进,展示出个人学习的深度,看视频轻松掌握高频 JavaScript 手写面试题:

参考自:寻找海蓝96 / 22 道高频 JavaScript 手写面试题及答案

视频列表

秋招还在进行中, 余下的视屏也在录制中, 最后祝大家取得好offer, 人人都可以去大厂,拿高薪。

查看原文

赞 3 收藏 3 评论 0

旅梦飞起 关注了用户 · 2019-07-13

wmui @wmui

Blog: https://blog.86886.wang
GitHub: https://github.com/wmui
技术交流群:4882 68810

关注 169

旅梦飞起 赞了文章 · 2019-07-13

vue+node支持服务端渲染的博客系统

感悟

历时两个多月,终于利用工作之余完成了这个项目的1.0版本,为什么要写这个项目?其实基于vuejs+nodejs构建的开源博客系统有很多,但是大多数不支持服务端渲染,也不支持动态标题,只是做到了前后端分离,对于博客类系统seo肯定很重要,索性就自己动手写了这个项目,其中也遇到了不少问题, 因为基于服务端渲染的项目不多,自己能力也有限,所以用了好长时间。这里特别感谢@lincenying,提供了登录功能的解决思路,也是我在开发过程中遇到最难解决的问题,本项目基于vue-hackernews-2.0开发,支持PWA(需升级为https),演示地址:https://www.86886.wang,项目地址:https://github.com/wmui/essay

开发环境和技术栈

操作系统:windows 10 64位
开发工具 :webstrom sublime
前端:vue.js + vue-router + vuex
后端:node.js + mongodb (采用express框架)

特色功能

支持服务端渲染
支持标题动态切换
支持PWA
支持markdown语法,样式采用github风格,代码高亮
支持文章保存为草稿
支持标签和归档功能

pc端效果图

首页效果图

代码高亮效果图

后台发布页面

后台文章列表

修改个人信息

手机端效果图,以chrome浏览器演示

添加到主屏

启动效果

首页效果

文章页效果

更多效果大家可通过线上演示地址查看

本地运行项目

  1. 安装mongodb并启动
  2. 安装git工具
  3. 克隆项目到你的本地
  4. 修改配置项信息,/server/settings.js,你也可以默认不修改,默认用户名:q,默认密码:q
let user = 'q';
let pass = md5('q');
let avatar = 'avatar.jpg';//头像
let intro ='Never too old to learn';
let nickname = 'vueblog';
module.exports = {
    dbUrl:'mongodb://localhost:27017/vueblog',
    user:user,
    pass:pass,
    avatar:avatar,
    intro:intro,
    nickname:nickname
}
  1. 打开本地终端,执行npm run dev ,访问http://localhost:8080

结语

关于如何部署到线上和部署https,后面会更新相关教程。此项目我会长期更新,希望能和大家一起学习,共同进步

更新: 本项目的2.0版本基于Nuxt.js开发,后端用Koa + Mongoose进行了重写。

GitHub: https://github.com/wmui/essay

查看原文

赞 66 收藏 256 评论 34

旅梦飞起 发布了文章 · 2019-04-16

大厂h5开源视频系列-京东校招宣传

前言

大厂h5开源视频系列 是一个专题,在这个专题中我们会解析一些酷酷的线上h5,在每一个细节,每一个细腻的过渡背后都能看到前端工程师付出的心血,本篇带来的是?? 京东校招宣传

线上链接请戳??京东校招宣传
小伙伴们可以自行感受一下,每一屏的动画加上背景音乐是不是让你不由自主的 WANNA DAN DAN DAN DAN DANCE,

就想一心投入京东物流事业(送快递的)行业中。

我们一起学习一下这个h5里面比较有意思的几个部分

学完之后希望大家早日能去和东哥做兄弟?

loading 部分

每个h5的loading,为了减少用户焦虑,各家可谓是费劲心思,最常见的有转圈,进度条...

分析:

  • 一个长方体上下运动,左右旋转
  • 周围竖线从上往下运动

我们先构造html结构,一个立方体,如gif我们只要5个面,底部那个面是不需要的,加上四根竖线:

<div class="loading" id="J-loading">
    <div class="c-cube-1">
      <div class="c-cube">
        <div class="c-cube__side c-cube__side--front"></div>
        <div class="c-cube__side c-cube__side--back"></div>
        <div class="c-cube__side c-cube__side--right"></div>
        <div class="c-cube__side c-cube__side--left"></div>
        <div class="c-cube__side c-cube__side--top"></div>
      </div>
    </div>
    <div class="line line-1"></div>
    <div class="line line-2"></div>
    <div class="line line-3"></div>
    <div class="line line-4"></div>
    <p class="progress-text origin-font" id="J-progress-text">66%</p>
  </div>

布局中涉及的难点

  • 立方体绘制

主要还是运用 transform (rotateY, rotateX, translateZ) 把每个面平移旋转之后固定在该放的位置

  • 立方体边框绘制:是红色到白色渐变的,关键代码
border: 1px solid;
border-image: linear-gradient(#ff1224, rgba(255, 18, 36, 0)) 2 100%;
border-top: none;
border-bottom: none;
height: 5em;
width: 1em;

重要属性 border-image,这个属性可以说是CSS3中的一员大将,还不了解的同学可以参考

鑫旭老师的 border-image 讲解

布局完了之后就到了我们的 动画

立方体的上下运动我们只要控制 translateY方向上的位移就好,animation-direction:alternate 让动画正向反向播放。

.loading .c-cube-1 {
  animation: cube-up-down-1 .8s ease-in-out infinite alternate;
}
@keyframes cube-up-down-1 {
  0% {
    transform: translateZ(0)
 } to {
    transform: translate3d(0, -.4rem, 0)
}
}

左右旋转,设置的就是 rotateY

.loading .c-cube {
  animation: spin-cube-right 1.2s linear infinite;
}
@keyframes spin-cube-right {
  0% {
    transform: rotateX(-33.5deg) rotateY(45deg)
  }
  to {
    transform: rotateX(-33.5deg) rotateY(225deg)
  }
}

第一屏 Slogon 页面


html里面放的是一句口号

<div class="slogon">这是一家以速度见长的公司</div>

最终显示给用户看的是一个背景图片,那么文字哪里去了呢?
这里作者使用了

text-indent: -133.32rem; 

将文字缩进到了一个用户看不到的地方,虽然看不到,但有利于 SEO。
我们的 slogn(你很年轻理应更快),从下往上淡入。
注意:animation-fill-mode: both; 就是 forwards, backwards 的结合。

.p1.swiper-slide-active .slogon {
  animation: fadeInUp .5s .3s both;
}
@keyframes fadeInUp{
  0% {
    opacity: 0;
    transform: translate3d(0,100%,0);
  }
  100% {
    opacity: 1;
    transform: none;
  }
}

后续几屏 都是以 快 为主题的场景页面

这几屏有几个特点,一个快字,一个宣传语,多是布局


里面的数字滚动,作者采用了

odometer 一个数字滚动的css/js库
外加一个自定义的字体

这里就不过多介绍

燃 这个主题页


分析:

  • 文字从右往左依次淡入
  • 两根立体柱子的倾斜方向运动

html基本结构

<div class="swiper-slide p8">
        <div class="logo-ran">燃</div>
        <div class="text-slogon">
          更快到达你想要的未来
        </div>
        <div class="cube-cnt-2">
          <div class="cube-2">
            <span></span>
          </div>
        </div>
        <div class="cube-cnt-3">
          <div class="cube-3">
            <span></span>
          </div>
        </div>
        <div class="text-title">
          京东2018校园招聘
        </div>
        <div class="text-detail">
          招聘人群: 2018年毕业<br>
          全日制本科、硕士、博士
        </div>
        <div class="logo-jd">京东招聘</div>
</div>
  • 第一部分文字从右往左淡入,透明度的变化 + translateX方向的偏移
@keyframes fadeInRight {
  0% {
    opacity: 0;
    transform: translate3d(50%,0,0);
}
100% {
    opacity: 1;
    transform: none;
}
}

那怎么依次进入呢?

.p8.swiper-slide-active .logo-ran {
  animation: fadeInRight .5s .3s both;
}
.p8.swiper-slide-active .text-slogon {
  animation: fadeInRight .5s .4s both;
}
.p8.swiper-slide-active .text-title {
  animation: fadeInRight .5s .6s both;
}
.p8.swiper-slide-active .text-detail {
  animation: fadeInRight .5s .7s both;
}

答案是:animation-delay可以做到

  • 第二部分,立体柱子怎么做的呢?


柱体:是一个背景渐变

background-image: linear-gradient(-90deg,hsla(0,0%,100%,.5),hsla(0,0%,100%,0));

HSLA 和 RGBA 一样是两种色彩模式,RGBA指的是“红色、绿色、蓝色、Alpha透明度,HSLA指的是“色调、饱和度、亮度、Alpha透明度” 。

柱顶:是一个正方形 旋转 + 倾斜

width: .70667rem;
height: .70667rem;
background-color: #fff;
transform: rotate(51deg) skew(20deg);

这里真的惊呆了,感叹作者的构思巧妙,一个障眼法让用户看上去就像立体的柱子。
同样我们柱子倾斜移动也是需要用到 transform:translateY translateX

.cube-cnt-2 {
    animation: cube-cnt-1-anim 1.5s ease-in-out infinite alternate;
}
@keyframes cube-cnt-1-anim {
    0% {
        transform: translateZ(0);
    }
    100% {
        transform: translate3d(.10667rem,-.10667rem,0);
    }
}

招聘流程


这一屏的实现基本如上面所讲的,文字依次淡入,这里小伙伴们不妨自己尝试一下。

如上便是这几屏的分析。
EOF 全文完?。

源码

这里把 loading 页面和其他页面分开

loading页面戳此

其他页面戳此

你可在 github上获取源码
你可以跟着 开源视屏 一步步实现, 有任何问题可以加入文末群聊交流。

预告

下篇我们将带来 网易云音乐2018年度总结 的分享,内容依旧精彩。

大家在学习的时候是不是只单独知道每个css属性,综合例子没有思路,不知道怎么动手,快快结合上面的讲解动起手来。

欢迎关注本系列文章,有问题可以加入???群聊和我一起讨论。

查看原文

赞 1 收藏 1 评论 0

旅梦飞起 发布了文章 · 2019-04-16

大厂h5开源视频系列-京东校招宣传

前言

大厂h5开源视频系列 是一个专题,在这个专题中我们会解析一些酷酷的线上h5,在每一个细节,每一个细腻的过渡背后都能看到前端工程师付出的心血,本篇带来的是?? 京东校招宣传

线上链接请戳??京东校招宣传
小伙伴们可以自行感受一下,每一屏的动画加上背景音乐是不是让你不由自主的 WANNA DAN DAN DAN DAN DANCE,

就想一心投入京东物流事业(送快递的)行业中。

我们一起学习一下这个h5里面比较有意思的几个部分

学完之后希望大家早日能去和东哥做兄弟?

loading 部分

每个h5的loading,为了减少用户焦虑,各家可谓是费劲心思,最常见的有转圈,进度条...

分析:

  • 一个长方体上下运动,左右旋转
  • 周围竖线从上往下运动

我们先构造html结构,一个立方体,如gif我们只要5个面,底部那个面是不需要的,加上四根竖线:

<div class="loading" id="J-loading">
    <div class="c-cube-1">
      <div class="c-cube">
        <div class="c-cube__side c-cube__side--front"></div>
        <div class="c-cube__side c-cube__side--back"></div>
        <div class="c-cube__side c-cube__side--right"></div>
        <div class="c-cube__side c-cube__side--left"></div>
        <div class="c-cube__side c-cube__side--top"></div>
      </div>
    </div>
    <div class="line line-1"></div>
    <div class="line line-2"></div>
    <div class="line line-3"></div>
    <div class="line line-4"></div>
    <p class="progress-text origin-font" id="J-progress-text">66%</p>
  </div>

布局中涉及的难点

  • 立方体绘制

主要还是运用 transform (rotateY, rotateX, translateZ) 把每个面平移旋转之后固定在该放的位置

  • 立方体边框绘制:是红色到白色渐变的,关键代码
border: 1px solid;
border-image: linear-gradient(#ff1224, rgba(255, 18, 36, 0)) 2 100%;
border-top: none;
border-bottom: none;
height: 5em;
width: 1em;

重要属性 border-image,这个属性可以说是CSS3中的一员大将,还不了解的同学可以参考

鑫旭老师的 border-image 讲解

布局完了之后就到了我们的 动画

立方体的上下运动我们只要控制 translateY方向上的位移就好,animation-direction:alternate 让动画正向反向播放。

.loading .c-cube-1 {
  animation: cube-up-down-1 .8s ease-in-out infinite alternate;
}
@keyframes cube-up-down-1 {
  0% {
    transform: translateZ(0)
 } to {
    transform: translate3d(0, -.4rem, 0)
}
}

左右旋转,设置的就是 rotateY

.loading .c-cube {
  animation: spin-cube-right 1.2s linear infinite;
}
@keyframes spin-cube-right {
  0% {
    transform: rotateX(-33.5deg) rotateY(45deg)
  }
  to {
    transform: rotateX(-33.5deg) rotateY(225deg)
  }
}

第一屏 Slogon 页面


html里面放的是一句口号

<div class="slogon">这是一家以速度见长的公司</div>

最终显示给用户看的是一个背景图片,那么文字哪里去了呢?
这里作者使用了

text-indent: -133.32rem; 

将文字缩进到了一个用户看不到的地方,虽然看不到,但有利于 SEO。
我们的 slogn(你很年轻理应更快),从下往上淡入。
注意:animation-fill-mode: both; 就是 forwards, backwards 的结合。

.p1.swiper-slide-active .slogon {
  animation: fadeInUp .5s .3s both;
}
@keyframes fadeInUp{
  0% {
    opacity: 0;
    transform: translate3d(0,100%,0);
  }
  100% {
    opacity: 1;
    transform: none;
  }
}

后续几屏 都是以 快 为主题的场景页面

这几屏有几个特点,一个快字,一个宣传语,多是布局


里面的数字滚动,作者采用了

odometer 一个数字滚动的css/js库
外加一个自定义的字体

这里就不过多介绍

燃 这个主题页


分析:

  • 文字从右往左依次淡入
  • 两根立体柱子的倾斜方向运动

html基本结构

<div class="swiper-slide p8">
        <div class="logo-ran">燃</div>
        <div class="text-slogon">
          更快到达你想要的未来
        </div>
        <div class="cube-cnt-2">
          <div class="cube-2">
            <span></span>
          </div>
        </div>
        <div class="cube-cnt-3">
          <div class="cube-3">
            <span></span>
          </div>
        </div>
        <div class="text-title">
          京东2018校园招聘
        </div>
        <div class="text-detail">
          招聘人群: 2018年毕业<br>
          全日制本科、硕士、博士
        </div>
        <div class="logo-jd">京东招聘</div>
</div>
  • 第一部分文字从右往左淡入,透明度的变化 + translateX方向的偏移
@keyframes fadeInRight {
  0% {
    opacity: 0;
    transform: translate3d(50%,0,0);
}
100% {
    opacity: 1;
    transform: none;
}
}

那怎么依次进入呢?

.p8.swiper-slide-active .logo-ran {
  animation: fadeInRight .5s .3s both;
}
.p8.swiper-slide-active .text-slogon {
  animation: fadeInRight .5s .4s both;
}
.p8.swiper-slide-active .text-title {
  animation: fadeInRight .5s .6s both;
}
.p8.swiper-slide-active .text-detail {
  animation: fadeInRight .5s .7s both;
}

答案是:animation-delay可以做到

  • 第二部分,立体柱子怎么做的呢?


柱体:是一个背景渐变

background-image: linear-gradient(-90deg,hsla(0,0%,100%,.5),hsla(0,0%,100%,0));

HSLA 和 RGBA 一样是两种色彩模式,RGBA指的是“红色、绿色、蓝色、Alpha透明度,HSLA指的是“色调、饱和度、亮度、Alpha透明度” 。

柱顶:是一个正方形 旋转 + 倾斜

width: .70667rem;
height: .70667rem;
background-color: #fff;
transform: rotate(51deg) skew(20deg);

这里真的惊呆了,感叹作者的构思巧妙,一个障眼法让用户看上去就像立体的柱子。
同样我们柱子倾斜移动也是需要用到 transform:translateY translateX

.cube-cnt-2 {
    animation: cube-cnt-1-anim 1.5s ease-in-out infinite alternate;
}
@keyframes cube-cnt-1-anim {
    0% {
        transform: translateZ(0);
    }
    100% {
        transform: translate3d(.10667rem,-.10667rem,0);
    }
}

招聘流程


这一屏的实现基本如上面所讲的,文字依次淡入,这里小伙伴们不妨自己尝试一下。

如上便是这几屏的分析。
EOF 全文完?。

源码

这里把 loading 页面和其他页面分开

loading页面戳此

其他页面戳此

你可在 github上获取源码
你可以跟着 开源视屏 一步步实现, 有任何问题可以加入文末群聊交流。

预告

下篇我们将带来 网易云音乐2018年度总结 的分享,内容依旧精彩。

大家在学习的时候是不是只单独知道每个css属性,综合例子没有思路,不知道怎么动手,快快结合上面的讲解动起手来。

欢迎关注本系列文章,有问题可以加入???群聊和我一起讨论。

查看原文

赞 1 收藏 1 评论 0

旅梦飞起 发布了文章 · 2019-04-16

大厂h5开源视频系列-网易云音乐年度总结

前言

大厂h5开源视频系列

是一个专题,在这个专题中我们会解析一些酷酷的线上h5,在每一个细节,每一个细腻的过渡背后都能看到前端工程师付出的心血,本篇带来的是 👉👉 网易云音乐年度总结

看视屏学习

欢迎关注以往的文章:

线上链接请戳 👉👉 网易云音乐年度总结

他并没有多震撼多酷炫, 那些花里胡哨的都没有,能形容的只有两个词:用心 细腻。网易云真的很用心,只要在 app 内部搜索【年度总结】你依然能看到自己的年度总结,在看到那首深夜还在听的歌,听过最多的歌词等瞬间,你是否回想起那个你爱过的女孩,是否能激起心里的涟漪。

分屏解析

1. 第二屏 概览


分解:

  • 右上角的文字淡入
  • 左下角一个人在音符上摆腿
  • 线条直接用了 svg (svg不了解的同学可以参考 流动的SVG线条

文字淡入

这个应该比较简单,我们上篇文章也讲解过:
只需要

  • opacity: 0 过渡到 opacity: 1;
  • transform: translateY(6px); 过渡到 transform: translateY(0px);
  • 越到上面的文字越早出现,给每一行文字设置不同的 transition-delay: 0.2s; transition-delay: 0.3s; transition-delay: 0.4s;

人在音符上摆腿

这里比较费心:

  • 布局:

可以看到除了音符外,这个人分为 头 身体 大腿 小腿 还有可爱的 jiojio


每一个部位都是定位定好的,可以看出原作者真的耗费很多心血。

<div class="id7_yinfu11">
    <div class="id6_tou">
      <div class="P1hair"></div>
    </div>
    <div class="id13_shenti"></div>
    <div class="id11_2xiaotui">
      <div class="id12_2jiojiopng"></div>
    </div>
    <div class="id8_1datui">
      <div class="id9_1xiaotui">
        <div class="id10_1jiojiopng"></div>
      </div>
    </div>
    <div class="id15_yinfu12"></div>
  </div>

音符摆动,左右摇摆即可。

animation: yinfu11 5s cubic-bezier(.445,.05,.55,.95) infinite;
@keyframes yinfu {
      0% {
        transform: rotate(-2deg);
      }

      51.67% {
        transform: rotate(2deg);
      }

      100% {
        transform: rotate(-2deg);
      }
}

小腿往前踢,rotate 负数即可。

@keyframes xiaotui {
      0% {
        transform: rotate(0deg);
      }

      51.67% {
        transform: rotate(-32deg);
      }

      100% {
        transform: rotate(0deg);
      }
}

2. 第三屏 在网易云听到最多的歌词

【永远】 两个词的出场实在太秀了,但其实实现还比较简单。
分解一下就是:

  • 文字有旋转
  • 并且渐渐放大
  • 有背景模糊

左右旋转就不过多介绍,
渐渐放大和背景模糊,背景模糊这里用到了 filter:blur(),整个动画如下:

@keyframes blurAppear {
      0% {
        filter: blur(10px);
        transform: scale(.8);
        opacity: 0;
      }

      100% {
        filter: blur(0);
        transform: scale(1);
        opacity: 1;
      }
}

3. 第四屏 特别的一天


分析一下最靓的一个部分当然是:

  • 秋千摆动
  • 人在悠闲的踢腿

整个秋千如下。

左右摆动,设置 rotate 即可,需要注意的是:

@keyframes ani4_qiuqian {
  0% {
    transform: rotateZ(0deg);
}
50% {
    transform: rotateZ(31.99359208deg);
}
100% {
    transform: rotateZ(0deg);
}
}

需要注意的是 这里角度较小,为了摆动的幅度大一点,可以设置
transform-origin: -16.10878661% -29.76878613%;

秋千完成之后,秋千上只有人的枝干,需要把腿,头,脖子等一一安放好。




之后就是摆腿,摇头的动画,我们上文已经提及,这里不过多介绍。

4. 第五屏 睡得最晚的一天


分析:

  • 一只猫
  • 一个人

这里作者做了分解,把不需要动的作为躯干固定好,比如人的躯干,猫的躯干。



之后把需要动的,比如猫尾巴,猫爪, 人手... 作为另外一个部分装上去。

5. 年度最喜欢歌手


这一屏,我们分解一下右上角的卷纸动画。

可以看到这其实是一个雪碧图,

@keyframes juanAnimation {
    0% {
        background-position: 0 0
    }

    3.70% {
        background-position: -483px 0
    }

    7.41% {
        background-position: -966px 0
    }

    11.11% {
        background-position: -1449px 0
    }
    ... 略过其中的几帧

    100.00% {
        background-position: -1449px -954px
    }
}

定好的动画,我们上下卷纸的时候只需要让其中的一个动画反向播放即可。

  animation: juanAnimation .5s steps(1) reverse,
  juanAnimation 1s steps(1) 1s forwards;

总结

在这个案列中其中很多动画并不难,但却是非常耗费工作量的,很多部位的拆分定位,而且越是细腻的动画需要的代码量也就越多。

源码

查看效果可戳此

你可在 github上获取源码
你可以跟着 开源视屏 一步步实现。

预告

下篇我们依旧带来 h5 的分享,内容依旧精彩。
最后, 感谢网易云音乐前端团队的创意作品,给予我们的灵感和一起学习的机会!

查看原文

赞 1 收藏 0 评论 1

旅梦飞起 发布了文章 · 2019-04-16

大厂h5开源视频系列-网易云音乐年度总结

前言

大厂h5开源视频系列

是一个专题,在这个专题中我们会解析一些酷酷的线上h5,在每一个细节,每一个细腻的过渡背后都能看到前端工程师付出的心血,本篇带来的是 👉👉 网易云音乐年度总结

看视屏学习

欢迎关注以往的文章:

线上链接请戳 👉👉 网易云音乐年度总结

他并没有多震撼多酷炫, 那些花里胡哨的都没有,能形容的只有两个词:用心 细腻。网易云真的很用心,只要在 app 内部搜索【年度总结】你依然能看到自己的年度总结,在看到那首深夜还在听的歌,听过最多的歌词等瞬间,你是否回想起那个你爱过的女孩,是否能激起心里的涟漪。

分屏解析

1. 第二屏 概览


分解:

  • 右上角的文字淡入
  • 左下角一个人在音符上摆腿
  • 线条直接用了 svg (svg不了解的同学可以参考 流动的SVG线条

文字淡入

这个应该比较简单,我们上篇文章也讲解过:
只需要

  • opacity: 0 过渡到 opacity: 1;
  • transform: translateY(6px); 过渡到 transform: translateY(0px);
  • 越到上面的文字越早出现,给每一行文字设置不同的 transition-delay: 0.2s; transition-delay: 0.3s; transition-delay: 0.4s;

人在音符上摆腿

这里比较费心:

  • 布局:

可以看到除了音符外,这个人分为 头 身体 大腿 小腿 还有可爱的 jiojio


每一个部位都是定位定好的,可以看出原作者真的耗费很多心血。

<div class="id7_yinfu11">
    <div class="id6_tou">
      <div class="P1hair"></div>
    </div>
    <div class="id13_shenti"></div>
    <div class="id11_2xiaotui">
      <div class="id12_2jiojiopng"></div>
    </div>
    <div class="id8_1datui">
      <div class="id9_1xiaotui">
        <div class="id10_1jiojiopng"></div>
      </div>
    </div>
    <div class="id15_yinfu12"></div>
  </div>

音符摆动,左右摇摆即可。

animation: yinfu11 5s cubic-bezier(.445,.05,.55,.95) infinite;
@keyframes yinfu {
      0% {
        transform: rotate(-2deg);
      }

      51.67% {
        transform: rotate(2deg);
      }

      100% {
        transform: rotate(-2deg);
      }
}

小腿往前踢,rotate 负数即可。

@keyframes xiaotui {
      0% {
        transform: rotate(0deg);
      }

      51.67% {
        transform: rotate(-32deg);
      }

      100% {
        transform: rotate(0deg);
      }
}

2. 第三屏 在网易云听到最多的歌词

【永远】 两个词的出场实在太秀了,但其实实现还比较简单。
分解一下就是:

  • 文字有旋转
  • 并且渐渐放大
  • 有背景模糊

左右旋转就不过多介绍,
渐渐放大和背景模糊,背景模糊这里用到了 filter:blur(),整个动画如下:

@keyframes blurAppear {
      0% {
        filter: blur(10px);
        transform: scale(.8);
        opacity: 0;
      }

      100% {
        filter: blur(0);
        transform: scale(1);
        opacity: 1;
      }
}

3. 第四屏 特别的一天


分析一下最靓的一个部分当然是:

  • 秋千摆动
  • 人在悠闲的踢腿

整个秋千如下。

左右摆动,设置 rotate 即可,需要注意的是:

@keyframes ani4_qiuqian {
  0% {
    transform: rotateZ(0deg);
}
50% {
    transform: rotateZ(31.99359208deg);
}
100% {
    transform: rotateZ(0deg);
}
}

需要注意的是 这里角度较小,为了摆动的幅度大一点,可以设置
transform-origin: -16.10878661% -29.76878613%;

秋千完成之后,秋千上只有人的枝干,需要把腿,头,脖子等一一安放好。




之后就是摆腿,摇头的动画,我们上文已经提及,这里不过多介绍。

4. 第五屏 睡得最晚的一天


分析:

  • 一只猫
  • 一个人

这里作者做了分解,把不需要动的作为躯干固定好,比如人的躯干,猫的躯干。



之后把需要动的,比如猫尾巴,猫爪, 人手... 作为另外一个部分装上去。

5. 年度最喜欢歌手


这一屏,我们分解一下右上角的卷纸动画。

可以看到这其实是一个雪碧图,

@keyframes juanAnimation {
    0% {
        background-position: 0 0
    }

    3.70% {
        background-position: -483px 0
    }

    7.41% {
        background-position: -966px 0
    }

    11.11% {
        background-position: -1449px 0
    }
    ... 略过其中的几帧

    100.00% {
        background-position: -1449px -954px
    }
}

定好的动画,我们上下卷纸的时候只需要让其中的一个动画反向播放即可。

  animation: juanAnimation .5s steps(1) reverse,
  juanAnimation 1s steps(1) 1s forwards;

总结

在这个案列中其中很多动画并不难,但却是非常耗费工作量的,很多部位的拆分定位,而且越是细腻的动画需要的代码量也就越多。

源码

查看效果可戳此

你可在 github上获取源码
你可以跟着 开源视屏 一步步实现。

预告

下篇我们依旧带来 h5 的分享,内容依旧精彩。
最后, 感谢网易云音乐前端团队的创意作品,给予我们的灵感和一起学习的机会!

查看原文

赞 1 收藏 0 评论 1

认证与成就

  • SegmentFault 讲师
  • 获得 6 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2015-06-05
个人主页被 649 人浏览