vue 单页嵌入live2D 卡通动图

效果类似如下:

会跟随随便移动。
人物都是事先生成好的,具体开发工具,后面补上。现记录加入步骤:
在打包的模板index.html 上加如下代码:

css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* // live 2d */
#landlord {
position: fixed;
font-size: 14px;
z-index: 6;
left: -26px;
bottom: -20px;
transform: scale(0.8);
}

#landlord .message {
text-align: center;
color: coral;
}

#landlord .hide-button {
position: absolute;
right: 0;
top: 0;
}

Dom

1
2
3
4
5
<div id="landlord">
<div class="message" style="opacity:0"></div>
<canvas id="live2d" width="280" height="250" class="live2d"></canvas>
<div class="hide-button hide">隐藏</div>
</div>

js

1
2
3
4
5
6
7
8
9
10
11
<script type="text/javascript" src="//cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script>
<script type="text/javascript">
var message_Path = '/static/live2d/'
var home_Path = '//katoto.cn/'
</script>
<script type="text/javascript" src="/static/live2d/js/live2d.js"></script>
<script type="text/javascript" src="/static/live2d/js/message.js"></script>
<script type="text/javascript">
loadlive2d('live2d', '/static/live2d/model/tia/model.json')
// loadlive2d("live2d", "/static/live2d/model/koharu/koharu.model.json");
</script>

然后webpack 打包使用 copy-webpack-plugin

1
2
3
4
5
6
7
8
9
10
11
12
13
const CopyWebpackPlugin = require('copy-webpack-plugin')
// ....
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}, {
from: path.resolve(__dirname, '../sitemap.xml'),
to: '../dist',
ignore: ['.*']
}
])

即可

vue2.x与vue3.x的响应差别

当前2.x的vue响应是基于Object.defineProperty的,其存在一些局限

  • 无法跟踪响应对象属性的添加 / 删除。
  • 无法监控到数组的下标变化 – vue里hack了八大数组的操作方法,像push\pop\shift等。
  • 在进行响应绑定时,如果属性值是对象,还需要进行深度遍历。

如何解决? 使用 Vue.set 和 Vue.delete 来保证。

1
2
3
* Object.defineProperty(obj, prop, descriptor)
* 定义或修改的属性描述符
* https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

下面我们来验证一下以上提到的几点。
案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* Object.defineProperty(obj, prop, descriptor)
* 定义或修改的属性描述符
* https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
*/
function definePropertyFn(data, prop, value) {
Object.defineProperty(data, prop, {
enumerable: true,
configurable: true,
get: function () {
console.log(`get key--- ${prop} ---- value ${value}`);
return value;
},
set: function (newVal) {
console.log(`set key--- ${prop} ---- value ${newVal}`);
value = newVal;
},
});
}
function observe(data) {
// 由于 Object.defineProperty 只能对属性进行劫持,因此需要遍历对象的每个属性
if (!data || typeof data !== "object") {
return false;
}
Object.keys(data).forEach((key) => {
definePropertyFn(data, key, data[key]);
});
}

这个时候,如何你observe一个数组,可以看出是没法正常触发getter or setter 的。

1
2
3
4
5
6
7
8
9
10
let arr = [1, 2, 3];
observe(arr);
document.getElementById("arrPush1").onclick = function () {
console.log("-------arrPush-----");
arr.push(4);
};
document.getElementById("arrPush2").onclick = function () {
console.log("------arr赋值-----");
arr[0] = new Date().getTime();
};

因而需要对数组shift push 等进行特殊处理。利用Object.create() 与 属性重定义,实现hack 处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
(function () {
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
const methodsToPatch = [
"push",
"pop",
"shift",
"unshift",
"splice",
"sort",
"reverse",
];
// 对特定的方法进行拦截,特殊通知
methodsToPatch.forEach((method) => {
const original = arrayProto[method];
Object.defineProperty(arrayMethods, method, {
enumerable: true,
writable: true,
configurable: true,
value: function (...args) {
// const result = original.apply(this, args);
const result = Reflect.apply(original, this, args);
switch (method) {
case "push":
console.log("拦截通知了push操作");
// emit & notify update
break;
case "splice":
case "pop":
console.log("拦截通知了xxx操作");
break;
}
return result;
},
});
});
// 看下是否通知
let testArr = new Array();
testArr.__proto__ = arrayMethods;
testArr.push("11111");
// 打开tool看下原型的展示
// console.log(testArr);
})();

有打开过vue2.x源码的,应该可以看出,上面的大致也是vue2.x 里源码的处理方式。打开node_modules 下的源码
core/observer/index.js

附上官方响应图:

vue3.x 使用的是ES6的Proxy 作为其观察者机制,取代之前的Object.defineProperty

有以下优点:

  • 可以劫持整个对象
  • 有13种形式劫持操作

还有一个与proxy 紧密使用的 Reflect
可以走下面这个test 进一步熟悉proxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* const p = new Proxy(target, handler)
* target 可以是任何类型的对象,包括原生数组、函数、甚至另一个代理
* https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
* https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
*/
let obj = {
a: 1,
ddd: 2222,
name: "hhh",
};
let proxyObj = new Proxy(obj, {
get: function (target, prop) {
console.log(`-------get-${prop}----`);
return target[prop] ? target[prop] : 0;
},
set: function (target, prop, value) {
// 可以做一些赋值校验
console.log(`-------set-${prop}-----`);
target[prop] = 8888;
},
getPrototypeOf: function (target) {
console.log(`-------getPrototypeOf------`);
// 读取代理对象的原型时,会被调用
return Array.prototype;
},
deleteProperty: function (target, prop) {
// 删除属性
const WhiteArr = ["name", "phone"];
if (WhiteArr.indexOf(prop) > -1) {
console.error("不可以删除该属性");
return false;
}
delete target[prop];
return true;
},
has: function (target, prop) {
// 判断对象是否具有某个属性
const WhiteArr = ["money", "phone"];
if (WhiteArr.indexOf(prop) > -1) {
console.warn("这是私有属性");
return false;
}
return true;
},
apply: function () {
// 函数调用的拦截 (代理的是函数)
},
construct: function () {
// new 操作的拦截
},
isExtensible: function () {
// 拦截 判断一个对象是否是可扩展
},
});
// 利用它进行数据的二次处理、可以进行数据合法性的校验
proxyObj.a;
proxyObj.b;
proxyObj.ccc = 666;
var aaa_prototype = Object.getPrototypeOf(proxyObj);
console.log(aaa_prototype === Object.prototype);
console.log(aaa_prototype === Array.prototype);

// 拦截delete 操作
delete proxyObj.name;
console.log(obj);
// 用 has方法隐藏了属性 money
console.log("money" in proxyObj);

基本认识proxy 之后,就可以升级个vue3.x 使用一番,可参考这个文章,一步步搭建vue-next 环境。

vue3.x 的各各模块,相对都是比较独立的,可以取单独的一部分进行使用,比如 Vue 3.0 的数据响应式系统是独立的模块,可以完全脱离Vue 而使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* Vue 3.0 的数据响应式系统是独立的模块,可以完全脱离 Vue 而使用
* https://juejin.im/post/6844903959660855309
* https://jrainlau.github.io/#/article?number=20
* yarn dev reactivity 调试
*/
const { reactive, effect } = VueReactivity;
const data = {
count: 1,
};
// 将data转成proxy对象state
const state = reactive(data);
const fn = () => {
const count = state.count;
document.getElementById("txt").innerHTML = count;
console.log(`set count to ${count}`);
9;
};
// effect把fn()作为响应的回调,当state.count 发生变化时,便触发了 fn()
// 默认会立即执行一次,进而把依赖进行收集
effect(fn);
// 点击事件
document.getElementById("btn").addEventListener("click", () => {
state.count++;
});
// 源码
// ├── compiler-core # 所有平台的编译器
// ├── compiler-dom # 针对浏览器而写的编译器
// ├── reactivity # 数据响应式系统
// ├── runtime-core # 虚拟 DOM 渲染器 ,Vue 组件和 Vue 的各种API
// ├── runtime-dom # 针对浏览器的 runtime。其功能包括处理原生 DOM API、DOM 事件和 DOM 属性等。
// ├── runtime-test # 专门为测试写的runtime
// ├── server-renderer # 用于SSR
// ├── shared # 帮助方法
// ├── template-explorer
// └── vue # 构建vue runtime + compiler

vue3.x 收集的大致流程如下,看源码就在reactivity 目录下。

参考链接
参考链接

下次梳理一下 typescript & hook

vue3.x结合typescript初体验

一、Vue3.0 的设计目标

  • 更小\更快 - Vue 3.0大小大概减少一半,只有10kB
  • 加强TypeScript支持
  • 加强API设计一致性 - 易读
  • 提高自身可维护性
  • 开放更多底层功能

vue3.x 采用Function-based API 形式组织代码,使其更容易压缩代码且压缩效率也更高,由于 修改了组件的声明方式,以函数组合的方式完成逻辑,天然与typescript 结合。(vue2.x中的组件是通过声明的方式传入一系列options的,所以在2.x下使用typeScript 需要装饰器的方式vue-class-component才行)

1
2
3
4
5
6
7
  // vue2.x 要想使用ts 需要这样处理,详见官方文档 https://cn.vuejs.org/v2/guide/typescript.html
<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'
@Component
export default class App extends Vue {}
</script>

二、typescript 有什么优点

1、增加代码的可读性与可维护性

  • 大部分函数看类型定义就知道是干嘛的
  • 静态类型检查,减少运行时错误

2、社区活跃,大牛都在用

  • 在vue3热潮下,之后typescript要成为主流,快学

三、搭建vue3.x + typescript + vue-router 环境

1、全局安装vue-cli

1
npm install -g @vue/cli

2、初始化vue项目

1
vue create vue-next-project

这里在输入命令后,需要选择Manually select features, 至少要把 babel typescript router 选上,(vuex 看自身情况是否需要)。如下图:

不清楚vue-cli 的可参考文章

3、升级为vue3.x项目

1
2
cd vue-next-project
vue add vue-next

这个命令会自动帮你把vue2.x升级到vue3.x ,包括项目中对应的依赖进行升级与模板代码替换,像:vue-router vuex

完成以上三步主体环境算搭建完成,看刚才创建的目录里多了个tsconfig.json 配置文件,可以根据自身与项目需要,进行配置。

接下来需要简单处理一下,使其支持typescript形式。(模板cli还没完善typescript的模板代码)

  • shims-vue.d.ts文件中的内容修改一下,这步操作应该会少了一些报错。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // declare module "*.vue" {
    // import Vue from 'vue';
    // export default Vue;
    // }
    declare module "*.vue" {
    import {defineComponent} from 'vue'
    const Component: ReturnType<typeof defineComponent>;
    export default Component
    }
  • 组件里使用需声明 script lang="ts" ,然后用defineComponent 进行包裹,即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<script lang="ts">
import {
defineComponent,
ref,
onMounted,
onUpdated,
onUnmounted,
} from "vue";

export default defineComponent({
name: "hello world",
props: {},
setup(props: any) {
const count = ref(0);
const increase = (): void => {
count.value++;
};
function test(x: number): string {
return props.toString();
}
test(1);
// test('number');
// 生命周期
onMounted(() => {
console.log("mounted vue3 typescript");
});
onUpdated(() => {
console.log("updated vue3 typescript");
});
onUnmounted(() => {
console.log("onUnmounted vue3 typescript");
});
// 暴露给模板
return {
count,
increase
};
},
});

</script>

生命周期对应

四、附上学习vue-next 与typescript 的官方秘籍

五、不想搭建你也可以直接拉去github demo

vue3+typescript环境

3-10 年内部岗位涉及前后端、PM(虾皮内推、乐信内推、vivo、oppo)推荐机会,欢迎联系,wx号: X915324 ;
也可发简历到: zgxie@126.com

H5页面加载过程

H5页面加载过程,主要包括2个部分:H5页面容器加载和资源文件加载。
1、H5页面容器加载过程明细如图:
对照浏览器window下的Performance的数据指标。

H5页面容器各关键点的时耗计算方式如下:

  • 准备耗时 = domainLookupStart - navigationStart;
  • 重定向耗时 = redirectEnd - redirectStart;
  • DNS解析耗时 = domainLookupEnd - domainLookupStart;
  • IP连接耗时 = connectEnd - connectStart;
  • 首包耗时 = responseStart - requestStart;
  • 完整包加载耗时 = 有两种计算方式,如果从网络加载,取值:responseEnd - requestStart;如果加载缓存:responseEnd - fetchStart;(从缓存加载,performance只有responseEnd,fetchStart有值,其他参数值都为0)
  • dom处理 = domComplete - domLoading;
  • 页面白屏时间 = domLoading - navigationStart;
  • 首屏时间 = 取值:loadEventStart- navigationStart; 前端开发也可根据业务侧理解的首屏手动打点tag,取值为:tagTime - navigationStart;
  • 可交互 = domContentLoadedEventEnd - navigationStart;
  • 页面完全加载耗时 = loadEventEnd - navigationStart;

2、UIWebView资源文件加载过程如下:


关键点的时耗计算方式如下:

  • 准备耗时 = domainLookup - fetchStart ;
  • DNS解析耗时 = domainLookupEnd - domainLookupStart;
  • IP连接耗时 = connectEnd - connectStart;
  • 首包耗时 = responseStart - requestStart;
  • 完整包加载耗时 = 有两种计算方式,如果从网络加载,取值:responseEnd - requestStart;如果加载缓存:responseEnd - fetchStart;(从缓存加载,performance只有responseEnd,fetchStart有值,其他参数值都为0)。

Performance 数据指标

1
2
3
4
5
6
7
8
9
10
11
n["DNS解析时间"] = e.domainLookupEnd - e.domainLookupStart,
n["TCP完成握手时间"] = e.connectEnd - e.connectStart,
n["重定向时间"] = e.redirectEnd - e.redirectStart,
n["html的ttfb耗时"] = e.responseStart - e.requestStart,
n["HTTP请求响应完成时间"] = e.responseEnd - e.requestStart,
n["DOM开始加载前所花费时间"] = e.responseEnd - e.navigationStart,
n["DOM加载完成时间"] = e.domComplete - e.domLoading,
n["DOM结构解析完成时间"] = e.domInteractive - e.domLoading,
n["脚本加载时间"] = e.domContentLoadedEventEnd - e.domContentLoadedEventStart,
n["onload事件时间"] = e.loadEventEnd - e.loadEventStart,
n["页面完全加载时间"] = n["重定向时间"] + n["DNS解析时间"] + n["TCP完成握手时间"] + n["HTTP请求响应完成时间"] + n["DOM结构解析完成时间"] + n["DOM加载完成时间"]

3-10 年内部岗位涉及前后端、PM(虾皮内推、乐信内推、vivo、oppo)推荐机会,欢迎联系,wx号: X915324 ;
也可发简历到: zgxie@126.com

参考: https://www.jianshu.com/p/47a6b7866ba6