# 2020年03月技术日常
# 2020/03/30 周一
# JSON数据转Blob后,怎么还原
在axios请求下载文件接口时,一般设置responseType: 'blob',文件返回正常就没问题,但后台如果处理文件或鉴权有问题,接口返回了包含错误信息的json格式数据,那样json数据也会转为Blob对象,而前端有必要将错误信息展示的,那怎么将Blob数据转JSON呢?下面来看看
let fileType = res.headers['content-type']
if (fileType.startsWith('application/json')) {
let reader = new FileReader();
reader.addEventListener("loadend", function() {
let data = JSON.parse(reader.result)
console.log(data);
});
reader.readAsText(res.data, "UTF-8") // 加UTF-8防止中文乱码
return
}
# 2020/03/28 周六
# 网页dark mode(深色模式)适配
微信最近推出了深色模式,我试了下,手机切换时页面效果样式是实时刷新的。于是就想着web怎么能够监听深色模式,并设置样式。查了资料后,在Stack Overflow上找到了答案
通过css里的媒体查询就能适配深色模式,先来看看怎么用js获取当前是否是深色模式
// 获取当前是否是深色模式
// window.matchMedia('(prefers-color-scheme: dark)').matches
window.matchMedia && console.log('Is dark mode: ', window.matchMedia('(prefers-color-scheme: dark)').matches)
// 用js监听深色模式的切换事件
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (event) => {
console.log('dark mode change,已' + (event.matches ? '进入': '退出') + 'dark mode')
})
window.matchMedia到底是用来做什么的?我查了下mdn,发现了这样一个示例
let mql = window.matchMedia('(max-width: 600px)');
document.querySelector(".mq-value").innerText = mql.matches;
从这个例子看,大概就知道怎么用css来支持dark模式了吧,就是加一个类似小屏适配的一个媒体查询样式,来看个例子
/* dark mode support */
@media (prefers-color-scheme: dark) {
body {
background-color: black;
color: #aaa;
}
body .content article, header, aside > div, footer {
border-color: #333;
color: #aaa;
background-color: black;
box-shadow: 0 0 10px #333;
}
}
深色模式下,一般将背景调暗,字体设置为偏白色即可。zuo11.com 已用上面的方法适配了深色模式,可以体验下。网站是开源的,zuo11.com深色模式支持代码 - github (opens new window)
参考:
- How do I detect dark mode using JavaScript? - Stack Overflow (opens new window)
- window.matchMedia | MDN (opens new window)
- Supporting Dark Mode in Your Interface | Apple Developer Documentation (opens new window)
# 2020/03/27 周五
# canvas绘制模糊的问题
今天发现同样的代码在两台电脑上绘制的一个清晰,一个模糊,后来查资料发现确实有这个问题
因为canvas不是矢量图,高dpi屏幕每平方英寸有更多的像素,也就是两倍屏,浏览器会以两个像素点的宽度来渲染一个像素,所以在Retina屏上会导致图片、文字都会模糊,怎么解决呢?
获取设备像素比:window.devicePixelRatio || 1
如果绘制的实际区域大小为 750 * 40,假设设备像素比为2,那么,canvas的width、height需要设置为 1500 * 80,然后用内联样式设置width为750,height为40,相当于canvas绘制2倍的大小,然后再缩放,这样就清晰了。
综上,在canvas绘制时,各种长度一定要考虑乘以devicePixelRatio,不然可能显示的不清晰
参考:解决 canvas 在高清屏中绘制模糊的问题 (opens new window)
# 2020/03/26 周四
# canvas不支持文本换行怎么处理
今天在stackoveflow里面搜索ctx.fill的问题时,查到了很多关于canvas ctx.fillText()绘制文本时不支持换行的问题,找到了一个比较好的答案
I'm afraid it is a limitation of Canvas' fillText. There is no multi-line support. Whats worse, there's no built-in way to measure line height, only width, making doing it yourself even harder!
一般解决思路是,根据 ctx.measureText('Hello').width 来看需要显示的文字是否需要换行,写一个for循环来处理
参考:
- canvas绘制文本内容自动换行 (opens new window)
- javascript - HTML5 canvas ctx.fillText won't do line breaks? - Stack Overflow (opens new window)
# canvas绘制不规则形状填充渐变色
在JS高程3中,有一章专门将使用canvas绘图,今天终于用上了,效果还不错,来看效果,原生js,70行不到
<canvas id="drawing1" width="720" height="45" >A draw of something.</canvas>
<script>
drawStatus('drawing1', 2)
function drawStatus(domId, position) {
let str = ['① 状态一', '② 状态二', '③ 状态三', '④ 状态四', '⑤ 状态五', '⑥ 状态六']
let config = {
width: 100,
height: 40,
extendLength: 20,
radius: 4
}
let config2 = { ...config, width: 110 }
let cur = str.length - position
str.reverse().forEach((item, index) => {
let pos = str.length - index - 1
let x = 0 + (str.length - 1) * config.radius
if (pos !== 0) {
x = (pos * 100) + (pos -1) * 10 + (str.length - 1 - pos) * config.radius
}
console.log(pos,x)
let curConfig = pos === 0 ? config : config2
if (pos < (str.length - cur)) {
curConfig.isFocus = true
}
drawUnnormalShape(domId, x , 0, str[index], curConfig)
})
}
function drawUnnormalShape(domId, x, y, text, config) {
let drawing = document.getElementById(domId);
let ctx = drawing.getContext('2d');
let { width, height, extendLength, radius, isFocus } = config
ctx.beginPath(); // 如果都需要重新beginPath 不然,后面的fill会覆盖前面的fill
// 不规则矩形
ctx.moveTo(x + radius, y) // 从左上角 (x + radius, y) 位置开始
ctx.arcTo(x, y, x, y + radius, radius) // 左上圆角
ctx.lineTo(x, y + height - 2 * radius) // 画左边
ctx.arcTo(x, y + height, x + radius, y + height, radius) // 左下圆角
ctx.lineTo(x + width - radius, y + height) // 下边
// ctx.arcTo(x + width - radius, y + height, x + width - radius, y + height - 1, 1) // 圆角
let extendEndX = x + width + extendLength
let middleHeight = y + height / 2
ctx.arcTo(extendEndX, middleHeight, extendEndX - radius, middleHeight - radius, radius) // 线 + 圆角
ctx.lineTo(x + width - radius, y)
ctx.lineTo(x + radius, y)
var gradient = ctx.createLinearGradient(x, y + height / 2, x + width + extendLength, y + height / 2); // 从(130,130)到(160,160)渐变
gradient.addColorStop(0, isFocus ? '#62ccff' : '#fff'); // 渐变的起点色
gradient.addColorStop(1, isFocus ? '#0486fe' : '#fff'); // 渐变的结束色
ctx.shadowOffsetX = 6;
ctx.shadowOffsetY = 2;
ctx.shadowBlur = 16; // 模糊像素
ctx.shadowColor = "rgba(58, 86, 111, 0.15)";
ctx.fillStyle = gradient
ctx.fill() // ctx.stroke()
let textArr = text.split(' ')
ctx.font = "15px arial"
ctx.fillStyle = isFocus ? '#fff' : '#ccc'
ctx.fillText(textArr[0], x + width / 2 - 20, y + height / 2 + 5)
ctx.font = "11px arial"
ctx.fillText(textArr[1], x + width / 2, y + height / 2 + 4)
}
</script>
参考之前的笔记:使用canvas绘图 - JS高程3笔记 (opens new window)
完整demo: canvas画不规则形状填充渐变背景 - github (opens new window)
# canvas 多次fill会覆盖前面的fill
在使用canvas进行绘图时,封装了一个绘制函数,每次都会填充颜色 ctx.fill(),如果多次执行,只会在最后一次时,整体fill一次?
刚开始以为是后面的fill覆盖了前面的fill,后来网上查了下,第一次fill后,再次fill需要再次调用ctx.beginPath(),不然只会在最后一次fill。
参考: HTML5的canvas标签为什么会覆盖之前画的东西的颜色 (opens new window)
# 2020/03/25 周三
# 根据文件名后缀判断文件类型不准确
比如我有个1.png文件,我修改下后缀名 1.txt,那前端如果仅凭文件名的后缀来显然是不行的,我们需要根据文件类型的二进制数据标记来判断对应的文件类型,这样才会更加准确,安全性更高
# 怎么判断两个文件一模一样
一般文件的md5可能会有重复的,怎么减少这种概率呢?校验分三个部分
- 比较整个文件的md5
- 选择文件固定位置的几个片段分别计算md5进行比对
- 比较文件名是否一样
# md5加密是可逆的吗?
理论上md5加密后,在不知道原始消息的前提下,是无法凭借16个字节的消息摘要(Message Digest),还原出原始的消息的.
但为什么有些网站可以破解md5加密后的密码呢?主要是使用的碰撞检测。它会提前算出一些常用弱密码的md5值,一个个比较。才会让人产生md5可逆的错觉。一般除了md5加密外,我们可以再多进行一些处理(加盐),来进行干扰,提高破解难度
参考:为什么说 MD5 是不可逆的? (opens new window)
# 两个不同文件md5可能一样吗?
什么是md5?md5 是 messge digest [daɪˈdʒest] 5 的缩写,意思是信息摘要算法
linux下,在terminal中执行man md5,可以查看对应的文档
md5 -- calculate a message-digest fingerprint (checksum) for a file
md5 -- 为一个文件计算信息摘要指纹('校验和'或'校验码')
md5 -s '123456'
# MD5 ("123456") = e10adc3949ba59abbe56e057f20f883e
md5 1.txt
# MD5 (1.txt) = 6f74626e0749e5353cc7e11767418d43
从上面的例子中我们可以看到,将文件或字符串进行 md5校验 会生成一个 32位 的校验码。问题来了,网上看到 md5加密后一般是128位,而这里只有32位为什么呢?我们要分清16进制与2进制,标准说法是,md5加密后的字符为 128bit(16字节),而一个我们看到的32位是16进制,每一位都可以转为4bit,也就是4个二进制位。1 - f 分别对应 0000 - 1111,所以128bit
一个二进制位(bit)只能表示0或1两种情况,128bit可以表示 Math.pow(2, 128) 2的128次方种情况,定死了,最多只能表示这么多种情况,不同内容的文件实在是太多了,理论上绝对是会超过2的128次方的。
综上:两个不同文件md5是有可能相同的,因为md5最多只能表示2的128次方种情况,而不同的文件绝对大于这个数
虽然两个文件的md5可能一致,但给定一个文件的md5值,想伪造另一个文件的md5值与该值一样,相对还是比较困难的,因此可用于判断文件完整性
参考: 有没有两个完全不一样的文件,但是他们的md5值是一样的? (opens new window)
# 2020/03/22 周日
# koa-multer与@koa/multer逻辑差异
之前有了解过以@开头的作用域包,这次在使用koa-multer这个模块时,发现@koa/multer与koa-multer的逻辑居然不一样。源码有些差异,下面来具体看看
// 在使用 koa-multer 时
const multer = require('koa-multer')
router.post('/test', multer().none(), ctx => {
let isFormData = ctx.headers['content-type'].startsWith('multipart/form-data')
// ctx.req node的request对象, ctx.request koa的request对象
ctx.body = isFormData ? ctx.req.body : ctx.request.body
})
koa-multer这个包是从express的multer包上面加了一层封装,而koa-multer并没有把fileds字段挂载到ctx.request.body上,只维持原来express那样挂载到node的request对象上,也就是ctx.req.body,来看看koa-multer的源码部分
// https://github.com/koa-modules/multer/blob/master/index.js
multer[name] = function () {
const middleware = fn.apply(this, arguments)
return (ctx, next) => {
return new Promise((resolve, reject) => {
middleware(ctx.req, ctx.res, (err) => {
err ? reject(err) : resolve(ctx)
})
}).then(next)
}
}
而@koa/multer则做了处理,可以与上面的例子对比下
// https://github.com/koajs/multer/blob/master/index.js
multer[name] = function() {
const middleware = Reflect.apply(fn, this, arguments);
return (ctx, next) => {
return new Promise((resolve, reject) => {
middleware(ctx.req, ctx.res, err => {
if (err) return reject(err);
if ('request' in ctx) {
if (ctx.req.body) {
ctx.request.body = ctx.req.body;
delete ctx.req.body;
}
if (ctx.req.file) {
ctx.request.file = ctx.req.file;
ctx.file = ctx.req.file;
delete ctx.req.file;
}
if (ctx.req.files) {
ctx.request.files = ctx.req.files;
ctx.files = ctx.req.files;
delete ctx.req.files;
}
}
resolve(ctx);
});
}).then(next);
};
};
我们再来看看使用@koa/multer的情况,就比较方便了,和其他数据一样挂载到 ctx.request.body
const multer = require('@koa/multer')
router.post('/test', multer().none(), ctx => {
ctx.body = ctx.request.body
})
综上,如果某个模块有两种包名,建议先考虑@开头的作用域包,通常这种功能会新点。后面迭代维护应该都是以这个为准
# 2020/03/20 周五
# npm包前面加@是什么意思(vue-cli与@vue/cli)
Vue CLI 的包名称由 vue-cli 改成了 @vue/cli。 如果你已经全局安装了旧版本的 vue-cli (1.x 或 2.x),你需要先通过 npm uninstall vue-cli -g 或 yarn global remove vue-cli 卸载它。
今天看vue-cli文档,发现上面的这段话 vue-cli 改为了 @vue/cli,这两个npm有什么区别呢?npm包前面加@是什么意思,查了下官网
npm包前面加@,代表scopes相关的包,可以理解为作用域(范围)包,作用域使我们可以创建与其他用户或组织创建的包同名,而不会发生冲突。
A scope allows you to create a package with the same name as a package created by another user or Org without conflict.
作用域名称是介于@和斜线之间的所有内容:
The scope name is everything between the @ and the slash:
// “npm” scope:
@npm/package-name
// “npmcorp” scope:
@npmcorp/package-name
npm包一个诟病就是包名很容易被占用的问题,占用后用其他人就不能用了。而作用域包类似于创建了一个命名空间,不同的命名空间,可以使用相同的包名
作用域的命名不是谁便就能用的,只有两种可以使用:自己的用户名、自己创建的组织名
注意:必须先注册一个npm用户帐户,然后才能发布用户作用域的npm软件包。此外,要发布组织作用域的软件包,您必须创建一个npm用户帐户,然后创建一个npm Org(组织)。
在 vue-cli 中可以用@vue/cli说明使用了vue这个npm账号或者组织发布了该包。
参考:
- npm学习(十)之如何使用创建、发布、使用作用域包 (opens new window)
- About scopes - Packages and modules | npm (opens new window)
- Creating and publishing scoped public packages | npm (opens new window)
# 2020/03/18 周三
# md怎么加引用注释,脚注
这里有一个注脚[^1],这段话的还有其他意思[^2]在里面
[^1]:这里是注脚内容
[^2]:这里是其他意思的注脚
注脚放到中间也可以
注脚放到中间也可以
# md中链接的另一种写法
我是一段文字,[baidu][1]、[qq][2]里面有链接
[1]: http://baidu.com "baidu"
[2]: http://qq.com "qq"
我是一段文字,baidu (opens new window)、qq (opens new window)里面有链接
# 2020/03/17 周二
# Node.js的核心用处及应用场景
打包构建、工程化,主要依赖基础的fs模块,文件读写,如xxx-cli(脚手架)、webpack、parcel、hexo,node在打包构建、前端工程化这块基本影响了整个前端的开发过程,各框架基本都有基于node的cli,快速生成脚手架,使开发更加高效、规范。
写后台接口,主要依赖基础的http模块,处理请求和响应,如 express.js、koa.js,一般主要用于模拟假数据接口, 调UI、交互效果以及做一些请求响应方面的自测
综合应用:获取数据+渲染页面(高并发、高性能),koa.js对于开发商业化应用来说还是比较单薄,egg.js基于koa做了一些增强,让node也可以做企业级应用。阿里的使用场景就是一个很好的例子,基础设施大部分采用 Java 实现,变化较少,有事务要求的 Business Services 通常使用 Java。而Node主要用于替代过去php、jsp使用场景, 用在需要快速迭代,需求变化非常快的用户侧。node已经经受了阿里双11的考验,技术上是可行的。
题外话:个人认为综合应用这块,自己玩玩还可以,小团队或node不是非常强的技术团队尽量不要尝试,阿里能做好这块是因为国内顶尖的node方面人才基本都在阿里,经过多年实践踩坑,拥有相对完善的node基建和生态。目前市面上前端里node强的比较少,饿了么为了招node服务端开发,还专门写了个node相关的面试教程。可想而知这方面人才有多少。
node支持高并发的原因:
- node.js基于异步I/O,接收到请求后,直接开一个I/O线程去执行,然后就不管了,立即继续执行主线程。等I/O线程执行完成后,直接执行对应的回调函数即可。省去了许多等待请求的时间
- 事务驱动,主线程通过event loop事件循环触发的方式来运行程序,这一条暂时还不是很理解,先写上~
参考:
- 如何评价阿里开源的企业级 Node.js 框架 EggJS? (opens new window)
- Node.js:浅析高并发与分布式集群 (opens new window)
- 天猫双11前端分享系列(四):大规模 Node.js 应用 (opens new window)
- egg.js (opens new window)
- node-interview | ElemeFE (opens new window)
# webpack与parcel区别
webpack与parcel都是打包工具,webpack功能强大,但比较重,配置项比较多,有点繁琐。而parcel就是为了解决配置项太多这个问题的,它默认集成了通用的常规功能,零配置,如果自定义较多,还是推荐webpack
If you don't want to worry about configuring everything and your needs are common needs, you should go directly with parcel. Parcel provides defaults (for babel-preset-env, post-css, html, etc) that fits most scenarios and works for everybody. You don't have to worry about configuring anything.
From the other hand, if you need a more customization, you should go with webpack. Keep in mind that you will have to setup everything that you need, explicitly set those things.
参考:Webpack vs Parcel - Stack Overflow (opens new window)
# mac使用触控板拖动复制、移动窗口
今天才意识到,每次要复制一段文字或移动某个应用窗口,我都是点击触控板再拖动。而且Mac Air的触控板按的声音比较响,于是找了下是否有手势可以支持。
发现可以设置使用 三指拖移 复制文字和拖动窗口,三个手指放上去拖动就可以了。
设置方法:打开系统统偏好设置 => 点 “辅助功能” => 点 “鼠标与触控板” => 点 “触控板选项” => 先勾上启用拖移,然后选择“三指拖移”,点击 “好”
图文详情参考: MacBook触控板选中/复制 (opens new window)
# 2020/03/12 周四
# 关于vue和react思考
我个人理解 vue组件设计的对新手超级友好,使用门槛很低。虽然我没用过react,但我可以猜到React应该也是牺牲了一定的用户使用门槛,来实现更加灵活的js控制。而vue牺牲了用js控制的灵活度来降低了用户使用门槛。侧重点不一样,但底层实现基本都差不多,比如虚拟dom、hooks,只是开放给用户的调用方式不一样。
前端框架再怎么变动,就算弄出一朵花来,也只是对dom的修改,最终还是会回归到 js高程3里面讲到的dom章节部分内容。
# git commit 提交信息有误怎么修改
如果不小心提交了,但没有push,可以使用下面的命令来修改上一次的commit信息
git commit --amend -m 'xxx'
# 他人提交了package-lock.json的更新导致拉取时和本地冲突
一般在npm install 时会修改package-lock.json文件,我一般不会提交这个更新,但今天发现有人提交。我拉取时,提示这个文件冲突,导致拉取不下来,我又不想提交更新,所以尝试用下面的命令,将工作区该文件的修改丢弃,再拉取
git checkout -- package-lock.json
拉取成功后,npm run serve 基本没什么影响
# 2020/03/11 周三
# vue封装组件方式的思考
在封装组件时,一般我们使用的方法是
把组件单独放到一个xx.vue,然后需要引入时在components使用懒加载引入再使用
我就在想,每次引入组件都需要三步
- 把组件通过 components 引入
- 在template中写对应的代码
- 在data中写对应的数据,methods里写绑定的事件
会不会太麻烦了,我希望像element的组件那样,通过 this.$message.error(e.message) 这样直接调用一个组件
于是我尝试使用js的方式来调用单文件组件(.vue),在之前02/20号写过方法,除了直接挂载到body外,也可以挂载到任何地方,只要你能拿到对应的dom,可以使用ref属性,再通过this.$refs['xx']来获取其DOM 元素和组件实例。
// 引入该组件
import ShowInfo from 'showInfo.vue'
// 通过js调用
clickShow() {
// 创建一个vue组件
const Component = Vue.extend(ShowInfo)
// 在文档之外渲染并且随后挂载,返回对应的Vue实例(vm)
let showInfoVue = new Component().$mount()
// 将组件实例的dom,append到当前页面body下
this.$el.appendChild(showInfoVue.$el)
}
其实你发现没,用js直接调用vue组件可以是可以,但也要比正常情况下写更多代码,比如
- js调用vue的方法需要封装为一个class
- 以上面的示例为例子,通过js调用组件,我们需要一个成功的回调,以及传参到组件,在获取到ShowInfo时,我们需要知道我们引入的只是一个'js对象'。我们可以在对象的methods里面注入方法,用来获取传入的值,或挂载成功后的回调。这样相当于mixin,但.vue组件实现里使用这些注入的事件时会不好理解,有种默认其妙多出来全局函数的疑惑
- 以js方法写的组件,不能兼容默认的引用方法,如果要支持那就要写一些额外的代码
- 你会发现逻辑会变的不好理解,不够简单,对新手不友好,如果需要其他人维护时,可能不好理解为什么这么做
综上所述:默认的封装调用组件的方式就很好,简单、明了,你想在调用的时候轻松,那么在封装组件时,就会增加对应的工作量,整体工作量差不多。
我们再来看element组件,对于内容比较少的,比如通知类,element都提供的是js调用方法,而没有普通的组件调用方法。且一般挂载到body上。为什么dialog组件没有封装成js呢?我的理解是dialog里面的内容可扩展性很强,如果改为js调用,可能会出现把大量代码写在js的情况,或者需要写VNode的render方法。就显得不够优雅了。
总结:在封装组件时,什么时候用封装为js调用方式、什么时候采用普通的封装呢?我的理解是可以通过下面几个方面来进行评估
- 被封装的组件需求是否稳定,有没有可能会在后面经常变更或者进行渐进式增强,如果不稳定,不建议封装为js调用方式,对于可扩展性强的,还是建议使用普通组件封装方式,更利于维护
- 是否是挂载到body下,还是需要放到任意的div内? 一般普通组件更好放置。如果挂载到body下,可以考虑封装为js调用方式
- 是否功能相对单一简单,如果组件功能单一简单建议封装为js调用方式
- 该组件是否在页面里大量被调用 大量的被调用,意味着大量重复的代码,可以考虑封装为js调用方式,增加组件实现时的复杂度,来降低调用时的复杂度
总之,不管怎么样封装,当有人问你为什么这么封装时,你要能够说出你自己的理由。
# 2020/03/10 周二
# vue自定义组件v-model属性实现dialog组件的二次封装
当某个组件是element的dialog组件时,我们需要对dialog进行隐藏显示,当子组件里的dialog关闭时,需要修改父组件传入的值,尽管不是表单组件也可以使用v-model来解决,先来看看怎么调用
<template>
<div>
<user-selection v-model="showUserSelection" @confirm="confirm"/>
<el-button type="primary" @click="showUserSelection = true">打开弹窗</el-button>
<div>
<template>
<script>
export default {
components: {
UserSelection: () => import("../src/components/user-selection/src/main")
},
data() {
return {
showUserSelection: false
}
},
methods: {
confirm(value) {
console.log(value)
}
}
}
</script>
再来看组件实现
<template>
<div>
<el-dialog title="提示" :visible.sync="dialogVisible">
内容
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
model: {
prop: 'show', // 设置对应v-model的属性字段
event: 'close' // 如果不指定默认为input,当$emit该事件,可以自动执行 修改父组件v-model参数的值
},
props: ['show'], // 接收v-model的传值
computed: {
dialogVisible: {
get() {
return this.show
},
set(newVal) {
console.log(newVal)
this.$emit('close', newVal)
}
}
},
data() {
return {}
}
}
</script>
# 封装组件时预留vue插件入口便于全局引入
在element组件中,我们使用el-input等element元素时,不需要在components里引入。为什么呢?在引入element时,我们有在mian.js里我们使用了Vue.use(elemnt组件),这样进行了全局注入组件,相当于组件做成了一个vue插件,如果我们自己封装组件如何能够在Vue.use后直接可以全局调用呢?
于是我特意去看了下element组件源码,这里我们暂时不要求封装为npm包,只需要在平常自定义组件的基础上做一个增强,可以使用Vue.use全局引入。
element-ui源码中,以alert组件为例,来看目录结构
alert
├── src
│ └── main.vue # 组件实现
└── index.js # install方法,供全局引入
import Alert from './src/main';
/* istanbul ignore next */
Alert.install = function(Vue) {
Vue.component(Alert.name, Alert);
};
export default Alert;
综上:我们在开发组件时,可以增加全局引入的接口,层级也可以仿照element的来,多研究源码这样代码才会写的更健壮。
# css hover后改变其他元素样式
css中某个元素hover后,可以对其他元素设置样式,但注意:只限定于改变他的子元素, 以及其后面的元素
<style>
/* hover后单独改变某一个子元素的样式 */
.cur-element:hover .child-1 {
color: red;
}
/* 设置相邻的后一个兄弟节点样式 */
.cur-element:hover + div {
background: blue;
}
/* 设置后面的所有对应的兄弟节点样式,不必相邻,但需要再其后面 */
.cur-element:hover ~ div {
background: red;
}
</style>
<div class="parent">
<div>再前一个兄弟元素</div>
<div>前一个兄弟元素</div>
<div class="cur-element">
测试hover
<span class="child-1">child-1</span>
<span class="child-2">child-2</span>
</div>
<div>后一个兄弟元素</div>
<div>再后一个兄弟元素</div>
<div>再再后一个兄弟元素</div>
</div>
参考:
- 之前的css笔记:css + ~ 选择器 (opens new window)
- css:hover状态改变另一个元素样式的使用 (opens new window)
- Change style of all other elements when one element is hovered - stackoverflow (opens new window)
# css vw的使用场景
在轮播图纯css的解决方案中:
- 将图片区域宽度设置为 图片张数 * 100%
- hover切换按钮时,将图片区域向左移动一个100%:transform: translateX(100%)
这里就会有问题,向左移动100%,宽度是图片张数*100%,而不是视窗宽度,用 100vw就可以很好的解决了。
参考之前的css笔记:css长度单位 相对长度 - HTML权威指南CSS部分 (opens new window)
# 2020/03/08 周日
# widnows nginx部署https服务
今天打算用使用 zuo11.com的 二级域名 api 来写接口玩玩,打算使用https。
- 在阿里云将域名免费的ssl证书分配给api.zuo11.com
- 在域名解析里,增加 api.zuo11.com 的解析,解析到服务器
- 初始化一个koa项目,监听某个端口,比如 9000端口,写一些测试的接口
- 部署到服务器
- nginx 添加对https的支持:①. 在ssl证书位置下载证书,会有两个文件 xxx.pem, xxx.key,在服务器nignx目录下的conf目录新建cert目录,将两个文件拷贝进去,修改conf下nginx.conf的配置
# HTTPS server
server {
listen 443 ssl;
server_name api.zuo11.com;
ssl_certificate cert\3391782_api.zuo11.com.pem;
ssl_certificate_key cert\3391782_api.zuo11.com.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
# 注释掉默认的加密方式
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; #使用此加密套件。
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #使用该协议进行配置。
ssl_prefer_server_ciphers on;
location / {
# root html;
# index index.html index.htm;
proxy_pass http://127.0.0.1:9000;
}
}
# 2020/03/07 周六
# nginx 中文路径404的问题
在mac本地调试时,都是ok的,部署到windows服务器上后时,发现一个图片出现了404的问题,最开始以为是缓存的问题,清了缓存后还是404。这张图片是中文路径,之前全部用的是英文的,没发现这个问题。试了下英文的图片链接是ok的。百度了下,发现确实有这种问题。是nginx设置的编码与操作系统的编码不一致的问题。
# linux查看电脑默认编码
echo $LANG
# zh_CN.UTF-8
# windows下查看默认字符编码
chcp
# 如果显示 活动代码页 936 表示GBK编码 我的服务器就是这个编码,修改了nginx charset utf-8;没效果
# 65001 表示utf-8
# 设置字符编码,但发现只在当前控制台生效,重新开一个就没了。
chcp 65001
# 有个修改注册表的方法:不知道是否可行,但怕影响服务器的其它服务,还是算了,改英文名比较稳。。。。
# https://blog.csdn.net/yangzhong0808/article/details/79012628?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
参考:windows下cmd命令行显示UTF8字符设置(CHCP命令) (opens new window)
# Invalid character in header content ["Content-Disposition"]
在koa中,如果Content-Disposition里设置文件名有中文会提示错误,需要用类似 encodeURIComponent 转码的函数转码后才行
const fileName = encodeURIComponent('这是一个文件') // 需要先转码才行
ctx.set({
'Content-Type': 'application/x-tar',
'Content-Disposition': `attachment; filename="${fileName}.tar"`
})
# 2020/03/03 周二
# github Badge 与npm 徽章图片生成
在写readme时,最开始一般会贴一些徽章图片,比如 build passing,license MIT 等,这些都是引入的图片
// 上图对应的三个徽章图片
![version-v0.2.0](https://img.shields.io/badge/version-v0.2.0-yellow.svg)
![build-passing](https://img.shields.io/badge/build-passing-green.svg)
![license-MIT](https://img.shields.io/badge/license-MIT-green.svg)
// 发现规律没,更改后面的参数,就可以生成不同的图片
// 比如
https://img.shields.io/badge/JAVA-1.8+-green.svg
npm 徽章图片,主要适用于npm包,显示npm包的一些信息
// 文档 https://www.npmjs.com/package/npm-badge
// zuo-blog npm包使用示例,只需要把对应的npm 包名称修改即可
[![NPM](https://nodei.co/npm/zuo-blog.png)](https://npmjs.org/package/zuo-blog)
# 个人小程序主体可以做什么
目前小程序名称变得很严格,只要包含稍微通用点的关键字,比如 "管理",都会要求上传手持身份证照片,且要有对应的商标证书。但貌似商标申请需要1000,且审批时间需要1年多... 太难了。而且个人小程序能做的东西越来越少了。
参考:个人主体小程序开放的服务类目 - 微信开放文档 (opens new window)
# 前端pc web获取时间是不准确的
我们在 new Date() 时,如果是在pc端,电脑时间不对,获取的时间也会不对。如果需要记录时间,应该发送请求给后端,让后端计算时间。依赖服务器时间
修改本地时间后,用 new Date() 获取时间,如下图会是错误的时间
# 2020/03/02 周一
# 浏览器tab页切换时更改标题
当用户点击了浏览器其他tab页离开页面,或者从其他tab页进入当前页,都会触发visibilitychange事件,根据docuemnt.hidden可以判断是否离开或回来,我们可以修改标题达到可视化的一个效果
// 实现tab间切换时,隐藏页面title改变功能
// JS高程3 Page Visibility API(页面可见性API)
// 参考:https://www.yuque.com/guoqzuo/js_es6/nocthb#0cf7a8b7
var title = document.title;
document.addEventListener('visibilitychange', function (event) {
document.title = document.hidden ? '~ 你快回来 ~ ' : title
if (document.hidden) {
// 做一些暂停操作
} else {
// 开始操作
}
}, false)
# 网页中网络异常或网络正常时动态提示
监听页面的online和offline事件,显示或隐藏对应的信息
// 当网络状态发生改变时(有网 => 无网,无网 => 有网),提示信息
// JS高程3 离线检测
// 参考: https://www.yuque.com/guoqzuo/js_es6/sp2k81#244d3090
let errorMsgNode // 用来移除错误信息节点
window.ononline = function(event) {
errorMsgNode && document.body.removeChild(errorMsgNode)
message('success', '网络已连接', 3000)
}
window.onoffline = function(event) {
message('error', '网络已断开')
}
/**
* 为了显示网络信息,专门写了个小tips提示函数,在顶部显示信息
* @param {}} type 文字颜色 error 为红色,其他为绿色
* @param {*} msg 显示信息
* @param {*} sec 如果有传入时间,sec秒后关闭提示
*/
function message(type, msg, sec) {
let color = type === 'error' ? 'red' : 'green'
let cssArr = [
'position:fixed;top:8px;left:50%;z-index:9999999;',
'transform:translateX(-50%);padding:5px 10px;background:#fff;'
]
let htmlStr = `
<div style="${cssArr.join('')}color:${color}">${msg}</di>
`
let node = document.createElement('div')
node.innerHTML = htmlStr
document.body.appendChild(node)
if (Number.isInteger(sec) && sec > 0) {
setTimeout(() => {
document.body.removeChild(node)
}, sec)
} else {
// 错误信息,一直提示,需要设置到变量里,等网络连接上时移除
errorMsgNode = node
}
}
# 页面滚动比例监听实现
监听页面的scroll事件,整个滚动距离为 document.documentElement.scrollHeight - window.innerHeight,当前scrollTop除以整个滚动距离,就是页面的百分比,向body挂载两个div来显示进度信息
// 页面滚动比例监听
// posTop 顶部类似阮一峰ES6网页的滚动进度条
// pos 右下角滚动百分比
// JS高程3 - UI事件 scroll事件
// https://www.yuque.com/guoqzuo/js_es6/elgng1#e38771e5
let htmlStr = `
<div id="posTop" style="position: fixed;top:0;height:2px;background: #25b864;z-index:999999;"></div>
<div id="pos" style="display:none;position:fixed;bottom: 100px;right:20px;padding:10px;background: #25b864;color:white;width:40px;text-align: center;border-radius:5px;"></div>
`
let eleNode = document.createElement('div')
eleNode.innerHTML = htmlStr
document.body.appendChild(eleNode)
window.addEventListener('scroll', function(e) {
let scrollTop = document.documentElement.scrollTop;
let total = document.documentElement.scrollHeight - window.innerHeight;
let persentage = parseInt(scrollTop/total*100);
// console.log(scrollTop);
document.getElementById('pos').style.display = scrollTop === 0 ? 'none' : 'block';
document.getElementById('pos').innerHTML = `${persentage}%`;
document.getElementById('posTop').style.width = `${persentage}%`;
}, false)
# 复制内容后,在内容中插入作者及当前文章信息
监听body里的copy事件,然后用 document.getSelection()获取内容,追加内容后,再使用event.clipboardData.setData像粘贴板里写入内容
// 操作粘贴板
// JS高程3 表单脚本 操作粘贴板
// https://www.yuque.com/guoqzuo/js_es6/ubpn7k#8482e7c5
document.body.oncopy = function(event) {
console.log('copy', event);
// 获取copy的内容
// console.log(document.getSelection().toString());
// 在copy内容里加入信息
var msg = `
-----------------------------
标题:${document.title}
链接:${location.href}
作者:guoqzuo (http://github/zuoxiaobai)
`
event.clipboardData.setData('text/plain', `${document.getSelection().toString()} ${msg}`);
event.preventDefault();
};