惨遭推倒重建
sysdog797 / syscoding Goto Github PK
View Code? Open in Web Editor NEWjust have fun
just have fun
顾名思义,就是点击按钮的时候,按钮上面有一个深色的圆圈扩散开来,就像是水面泛起的涟漪。简单分析下来,就是点击的时候,一个圆圈从小变大,同时不透明度也从半透明变成透明。
通常有两种方式来实现这个效果即JavaScript和CSS(这里主要记录CSS的方法,JavaScript简单介绍思路)。
这里先放上效果示意图:
首先看JavaScript实现方式:
- 监听鼠标的点击事件,当点击按钮的时候,追加一个圆形的DOM
- 改变DOM的大小和不透明度,直到能够完全的覆盖按钮
- 移除圆圈
这个方法有它的优点也有缺点,缺点主要有:
- 添加和移除DOM会导致页面的重绘
- 绘制的时候要进行运算
不过这个方法可以方便灵活的实现从鼠标点击所在的位置实现涟漪效果,这是CSS不好做到的。
那,再来看CSS的实现方式,先上代码(这里用了less):
.button-ripple() {
overflow: hidden;
position: relative;
transition: background-color .3s linear, border .3s linear;
&:after {
content: "";
display: block;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
pointer-events: none;
background-image: radial-gradient(circle, #000 10%, rgba(0, 0, 0, 0) 10.01%);
background-repeat: no-repeat;
background-position: 50%;
transform: scale(10);
opacity: 0;
transition: transform .5s, opacity 1s;
}
&:active:after {
transform: scale(0);
opacity: .2;
transition: 0s;
}
}
这是一个less的mixin,直接在需要使用的地方如下操作即可:
.ripple{
.button-ripple();
}
这里利用:after伪元素画了一个遮罩层,当点击的时候遮罩缩小到0%,透明度为0.2,松开鼠标时还原遮罩层100%的大小,并将透明度缩小到0。
最近业务不忙,抽空玩了一下微信小程序开发,现阶段我还停留在API玩家的level,所以文章的题目暂且叫作微信小程序初探,这里只是一个自己的踩坑和开发的小记录。
这点上官方文档还是讲的比较清楚的,参照这里的步骤配置好开发环境,编辑器还自带一个小demo帮助我们来熟悉小程序的基本操作,对于前端来说上手还是比较快的。
小程序的文件和方法很多都是以wx开头命名的。。我们知道网页编程采用的是 HTML + CSS + JS 这样的组合,其中 HTML 是用来描述当前这个页面的结构,CSS 用来描述页面的样子,JS 通常是用来处理这个页面和用户的交互。
同样道理,在小程序中也有同样的角色,其中 WXML 充当的就是类似 HTML 的角色。打开 pages/index/index.wxml,你会看到以下的内容:
<view class="container">
<view class="userinfo">
<button wx:if="{{!hasUserInfo && canIUse}}"> 获取头像昵称 </button>
<block wx:else>
<image src="{{userInfo.avatarUrl}}" background-size="cover"></image>
<text class="userinfo-nickname">{{userInfo.nickName}}</text>
</block>
</view>
<view class="usermotto">
<text class="user-motto">{{motto}}</text>
</view>
</view>
可以看到和 HTML 非常相似,WXML 由标签、属性等等构成。其中<button>、<image>等都是小程序内部的组件,这里我们不会用到<div>、<span>等标签都是直接用这些组件来实现页面构建。另外,使用过vue或者react的同学应该能够明白这里wx:if、{{motto}}这样的表达式是什么含义,这里就不再赘述了。
同CSS样式,几乎没有区别。注意下新增了尺寸单位。在写 CSS 样式时,开发者需要考虑到手机设备的屏幕会有不同的宽度和设备像素比,采用一些技巧来换算一些像素单位。WXSS 在底层支持新的尺寸单位 rpx ,开发者可以免去换算的烦恼,只要交给小程序底层来换算即可,由于换算采用的浮点数运算,所以运算结果会和预期结果有一点点偏差。
一个服务仅仅只有界面展示是不够的,还需要和用户做交互:响应用户的点击、获取用户的位置等等。在小程序里边,我们就通过编写 JS 脚本文件来处理用户的操作。
<view>{{ msg }}</view>
<button bindtap="clickMe">点击我</button>
点击 button 按钮的时候,我们希望把界面上 msg 显示成 "Hello World",于是我们在 button 上声明一个属性: bindtap ,在 JS 文件里边声明了 clickMe 方法来响应这次点击操作:
Page({
clickMe: function() {
this.setData({ msg: "Hello World" })
}
})
注意到这里使用了 setData 来改变 msg 的值,这里用法是和react一样的。
思考了一下,为了快速熟悉小程序的操作,这里选择做一个简单的Demo(参考 NextDay ,可以说是NextDay小程序复刻版),在开发的过程中还是遇到了几个坑的。
首先想到的就是小程序提供的组件 audio ,这里出现了一个问题。audio 在编辑器里面可以正常播放,但是在iphoneX上音频播放没有反应,看文档上注明了1.6.0 版本开始,该组件不再维护。后来替换了官方出的新组件 wx.createInnerAudioContext 解决了这个问题,这里有一个区别是 <audio> 是一个标签,在wxml中引用并在标签上绑定响应的方法,而 wx.createInnerAudioContext 则是一个全局方法,通过设置里面的url等信息来控制相关音频播放。
NextDay 是一个日历类的APP,通过滑动来实现每日的预览。这里尝试了三种swiper组件,WX的Swiper、weSwiper 和 hSwiper ,其中后两个是开源的Swiper组件。
<Swiper>:小程序内置的Swiper组件,通过标签的形式引入。这个组件有一个bug,在滑动的时候触发touch事件,滑块会停在中间动画停止。
we-Swiper:这个是参考著名的Swiper.js来开发的一个开源组件,比起微信内置的组件多了很多API调用,基本满足了Demo开发的需求,但是它有一个不足就是不能动态更新滑块的List(其实可以自己写...)。
hSwiper:这个开源组件除了一些开放的API还包括了动态更新滑块List的功能,所以Demo就选用了这个组件进行开发。
官方文档在这里,首先来看事件绑定与冒泡:
事件绑定的写法同组件的属性,以 key、value 的形式。key 以bind或catch开头,然后跟上事件的类型,如bindtap、catchtouchstart。自基础库版本 1.5.0 起,在非原生组件中,bind和catch后可以紧跟一个冒号,其含义不变,如bind:tap、catch:touchstart。
value 是一个字符串,需要在对应的 Page 中定义同名的函数。不然当触发事件的时候会报错。
bind事件绑定不会阻止冒泡事件向上冒泡,catch事件绑定可以阻止冒泡事件向上冒泡。
如在下边这个例子中,点击 inner view 会先后调用handleTap3和handleTap2(因为tap事件会冒泡到 middle view,而 middle view 阻止了 tap 事件冒泡,不再向父节点传递),点击 middle view 会触发handleTap2,点击 outer view 会触发handleTap1。
<view id="outer" bindtap="handleTap1">
outer view
<view id="middle" catchtap="handleTap2">
middle view
<view id="inner" bindtap="handleTap3">
inner view
</view>
</view>
</view>
再来看事件捕获:
自基础库版本 1.5.0 起,触摸类事件支持捕获阶段。捕获阶段位于冒泡阶段之前,且在捕获阶段中,事件到达节点的顺序与冒泡阶段恰好相反。需要在捕获阶段监听事件时,可以采用capture-bind、capture-catch关键字,后者将中断捕获阶段和取消冒泡阶段。
在下面的代码中,点击 inner view 会先后调用handleTap2、handleTap4、handleTap3、handleTap1。
<view id="outer" bind:touchstart="handleTap1" capture-bind:touchstart="handleTap2">
outer view
<view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
inner view
</view>
</view>
如果将上面代码中的第一个capture-bind改为capture-catch,将只触发handleTap2。
<view id="outer" bind:touchstart="handleTap1" capture-catch:touchstart="handleTap2">
outer view
<view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
inner view
</view>
</view>
现在小程序还在上传提审的阶段,后续上线发布的内容将会在下一篇博客里面补上。
在创建或注册模板的时候,传入一个data属性作为用来绑定的数据。但是在组件中,data必须是一个函数,而不能直接把一个对象赋值给它。
Vue.component('my-component', {
template: '<div>OK</div>',
data() {
return {} // 返回一个唯一的对象,不要和其他组件共用一个对象进行返回
},
})
然而在new Vue()的时候,是可以给data直接赋值为一个对象的。Why?
其实,上面这个操作是一个简易操作,实际上,它首先需要创建一个组件构造器,然后注册组件。注册组件的本质其实就是建立一个组件构造器的引用。使用组件才是真正创建一个组件实例。所以,注册组件其实并不产生新的组件类,但会产生一个可以用来实例化的新方式。
So,理解这点之后,再理解js的原型链:
var MyComponent = function() {}
MyComponent.prototype.data = {
a: 1,
b: 2,
}
// 上面是一个虚拟的组件构造器,真实的组件构造器方法很多
var component1 = new MyComponent()
var component2 = new MyComponent()
// 上面实例化出来两个组件实例,也就是通过<my-component>调用,创建的两个实例
component1.data.a === component2.data.a // true
component1.data.b = 5
component2.data.b // 5
可以看到上面代码中最后三句,这就比较坑爹了,如果两个实例同时引用一个对象,那么当你修改其中一个属性的时候,另外一个实例也会跟着改。这怎么可以,两个实例应该有自己各自的域才对。所以,需要通过下面方法来进行处理:
var MyComponent = function() {
this.data = this.data()
}
MyComponent.prototype.data = function() {
return {
a: 1,
b: 2,
}
}
这样每一个实例的data属性都是独立的,不会相互影响了。所以,你现在知道为什么vue组件的data必须是函数了吧。这都是因为js本身的特性带来的,跟vue本身设计无关。其实vue不应该把这个方法名取为data(),应该叫setData或其他更容易立即的方法名。
业务中经常会有需求,需要由事件冒泡来解决,最近看代码的时候遇到一个业务Bug,就是在React中阻止事件冒泡造成的,这里就记录一下解决方法。
合成事件:JSX中绑定的事件。
<a ref="aaa" onClick={(e)=>this.handleClick(e)}>更新</a>
原生事件:通过js原生代码绑定的事件。
document.body.addEventListener('click',e=>{
console.log('body');
})
this.refs.update.addEventListener('click',e=>{
console.log('update');
});
stopImmediatePropagation的定义在这里,那么他和stopPropagation的区别是什么呢..
- stopPropagation 能够阻止事件的进一步捕获或者冒泡。
- 假设事件流已经被某个元素捕获(或者冒泡到某个元素),那么便会触发此元素上绑定的事件。如果绑定的事件不止一个,则依次触发。假如想中断这种依次触发,可以调用 e.stopImmediatePropagation。
如果是React组件中的合成事件,那么stopPropagation就可以阻止事件的进一步冒泡,但是React中如果是合成事件和document之间的冒泡stopPropagation就不起效果。
观察下面这个例子:(参考资料)
看上去和Dom中冒泡的效果一样,但是我们都知道“React 组件绑定事件本质上是代理到 document 上”。也就是说,只有当事件流冒泡到 document 上时,才会依次触发 document 上绑定的两个事件。
事实并非#child 和 #parent 的事件分别代理到 document 上,而是 React 在 document 上绑定了一个 dispatchEvent 函数(至于这个函数怎么实现的,这里先深入...),在执行 dispatchEvent 的过程中,其内部会依次执行 #child 和 #parent 上绑定的事件。
- 事件流首先进入到 #child ,然后触发直接绑定在 #child 上的事件;
- 事件流沿着 DOM 结构向上冒泡到 document,触发 React 绑定的 dispatchEvent 函数,从而调用了 #child 子元素上绑定的 clickChild 方法。
- 在 clickChild 方法的最后,我调用了 e.stopPropagation,成功地阻止了 React 模拟的事件冒泡,因此,成功地没有触发 #parent 上的事件。
- 然后,最后出现了问题,还是触发了 document 上的事件。
所以这个时候stopPropagation就不管用了,需要用到stopImmediatePropagation。在React中,这里是e.nativeEvent.stopImmediatePropagation()
看下面这个流程图会更清楚些:
Vue 和 React 组件内的 this 指向的都是组件的实例,它们都可以通过修改实例数据来更新页面。区别在于,Vue 中的 this 没有提供 state / props / setState API,不论是父组件传入的数据还是子组件的数据,都在同一个 this 的作用域下(这样业务代码会更短一些)。并且,Vue 也没有 setState 这样的 setter 方法来更新数据,直接通过全量赋值 this.xxx = yyy 数据的方式就可以更新 DOM 了。
React 和 Vue 的设计区别,在更新数据的 API 中可以得到体现。Vue 的依赖追踪 Hack 了对象的 setter,因此在执行简单的赋值操作时可以直接获知状态树中的修改位置,但 DOM 更新是异步的。因此在 Vue 中如下的代码是没有问题的:
this.hasData = true
// 数据状态同步更改,但 v-if="hasData" 的元素还没有出现
this.hasData === true
需要显式 setState 的 React 则不能从基础的赋值操作中获知变更内容,而是需要通过 setState 触发一次 render,而后在 render 中更新 DOM 状态。因此这是一个容易踩的坑:
this.setState({ hasData: true })
// 这里有问题,必须在 setState 回调中数据状态才得到更改
this.hasData === true
React 中 DOM 状态和数据状态都是异步更新的。Vue 中仅 DOM 状态异步更新,需要保证 DOM 状态正确更新时,所使用的 Vue.nextTick() API 实际上也类似于 React 的 setState 回调。
官网解释:并不能保证this.state会被立即更新,因此在调用这个方法之后访问this.state可能会得到的是之前的值。不能保证调用setState之后会同步运行,因为它们可能被批量更新,你可以提供可选的回调函数,在setState真正地完成了之后,回调函数将会被执行。各种不保证、可能...
你会发现,下面的情况不会出现:
// 假设 state.count === 0
this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
// state.count === 1, 而不是 3
要解决这个问题可以像下面这么做:即使用setState() 的第二种形式 —— 以一个函数而不是对象作为参数,此函数的第一个参数是前一刻的 state,第二个参数是 state 更新执行瞬间的 props
// 正确用法
this.setState((prevState, props) => ({
count: prevState.count + props.increment
}));
当然了 setState(nextState, callback) 中的回调里面是更新后的state,可以在这里取到最新的值。一个小例子:
//1.设置代码
this.setState({
myState: 'test'
}, function() {
// stateFunction是需要立即用到
this.stateFunction()
})
//2.在函数中直接调用
stateFunction() {
console.log('myState value is :', this.state.myState)
}
或者采用更直接的方法,用 setTimeout 也可以获取到最新的state值:
this.setState({
myVal: 'test'
})
setTimeout(() => {
console.log(this.myVal);
},0)
终于部署了第一版博客(但是域名还没下来)还有很多不足的地方,比如浏览器兼容性问题(要不要考虑低版本IE呢 emmm...)。还有就是自适应的策略还有改进的地方,现在用的是rem,感觉体验不够好,目前的整体结构比较简单,也没有后台管理系统,以后慢慢完善(准备着手开发移动端版本了...)
由于github的API有访问次数的限制,同一个IP一小时只能访问60次,所以考略用MongoDB来存储文章数据,并且进行定时更新。文章列表和文章的详情就通过读取数据库来获得数据。
618期间,项目不能上线,是时候升级一下博客了(直接推倒进入重建模式...)
highlight被官方下架了,取而代之的是highlight.js,其实就是一个东西。发现云服务器装windows系统可以傻瓜式部署代码...
三个月后...加入了一个markdown的解析模块叫remarkable,方便好用,它会给不同的语言设置不同的属性。搭配使用的代码着色模块是highlight.js,这里有一个坑,因为整个markdown是异步加载进来的,直接用的话相关js不会执行,最后在Vue的main.js里面加了一个自定义指令解决。
继上个月好不容易启动工程以来,进度又停滞了两周...(室友两三天用Hexo+Github快速建站已经上线了,还能兼容移动端,找时间试试这个模板)修改确定了最后的UI,虽然还是不太满意...整个网站的流程前后端调通,通过request调用github的API来实现博客内容的管理。调用github API的时候有一个小坑,直接request数据的时候会返回一个403,提示需要一个User-Agent header,这里需要记得加上。
在购买服务器和域名审核过后一个月,终于拖拖拉拉开始启动了网站的搭建...整个网站采用Node、Express和MongoDB进行开发,框架选用的是Vue(强行加一个框架进来),网上各种各样的教程非常多,上手相对简单。
从github API拿到的数据需要在后台解析渲染,这里就是采用的remarkable,参考官方文档很容易就调通了。
var Remarkable = require('remarkable');
var md = new Remarkable();
console.log(md.render('# Remarkable rulezz!'));
// => <h1>Remarkable rulezz!</h1>
为了能够使代码能够高亮,这里采用了highlight.js插件进行代码着色。
<link rel="stylesheet" href="/path/to/styles/default.css">
<script src="/path/to/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
有个小坑,主要是内容是异步加载进来的,所以这个js执行不到。解决方案是vue的自定义指令,定义一个v-highlight的指令来使得pre code中的代码高亮。
// 在main.js定义自定义指令v-highlight
Vue.directive('highlight', function (el) {
// 当被绑定的元素插入到 DOM 中时……
let blocks = el.querySelectorAll('pre code');
blocks.forEach((block) => {
hljs.highlightBlock(block)
})
})
还有其他的方法,比如让js延迟加载,先加载html再加载js着色,这样的话需要加一个loading的界面进行友好提示(用route的中断器进行属性判断)。
由于第一版博客实在是写的太low,近期直接推翻重来。依然是用vue-cli进行项目搭建,这一次用的simple-webpack模式。
// 配置方法
vue init webpack-simple my-project
这种模式的好处在于,构建出来的项目很轻,只包含了基本的vue-loader和webpack配置(当然基本的热重载和build功能)。
import Vue from 'vue'
import App from './App.vue'
import VueResource from 'vue-resource'
Vue.use(VueResource); // 一定要放在这里
new Vue({
el: '#app',
render: h => h(App)
})
在vue-cli的simple-webpack模式中,webpack配置默认没有使用任何插件的,所有的插件都需要我们自己配置,这也能够让我们能更深刻的了解它的配置。
初始环境下,使用npm run build
打包出来的是一个个js文件(默认为bundle.js),要想打包出来是一个html文件和js文件就需要用到这个插件了。
// 下面是伪码(下同)
const HtmlWebpackPlugin = require('html-webpack-plugin');
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html', // 生成的html存放路径,相对于publicPath
inject: 'body', // js放在body元素底部
template: 'index.html' // html模板路径
}),
...
]
用于分离出css文件,配置相对简单
const ExtractTextPlugin = require("extract-text-webpack-plugin");
module: {
rules: [{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
},
extractCSS: true
// other vue-loader options go here
}
},
...
],
plugins:[
new ExtractTextPlugin("static/css/[name].css"),
...
]
}
打包文件的时候,通常会有一些静态文件(如reset.css、icon图片资源等)需要一并被打包到build出来的dist文件夹中,这时就会用上这个copy的插件了。
const CopyWebpackPlugin = require('copy-webpack-plugin');
plugins:[
new CopyWebpackPlugin([{
from: path.resolve(__dirname, './src/static'),
to: 'static'
}]),
...
]
上篇文章也说到过React通过state同一管理到数据,并通过this.setState()
更新(而且dom是异步更新的!);而Vue可以直接改变实例中相对应的数值,并且刷新dom。
React中,需要通过模板字符串或者if语句进行一些数据的判断,从而控制dom的显示隐藏。下面是一些例子:
// 情况一
let dom;
if(this.state.data){
dom = <div></div>;
}else{
dom = '';
}
// 情况二
<div className={`init-class${tihs.state.data ? '' : ' hide'}`}></div> //注意这里的空格
反观vue里面直接用v-if或者v-show来控制即可2333。
这里需要注意:v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
<div v-show="data" class="init-class"></div>
Vue中使用来控制一些过渡特效是一个神器,特别是结合v-show来写一些渐隐渐现的效果非常好用。搬运一个例子:
<div id="demo">
<button v-on:click="show = !show">
Toggle
</button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
在React生态里面有类似的插件,实现类似的动画react-transition-group,这个还是比较好用的,除了在处理元素隐藏显示的时候显得力不从心,这就显示处vue把动画和v-show结合在一起的好处了。
说了那么多React的“坏话”,其实React在也有自己的优势,在构建大型应用时其数据状态管理的透明度和可测试性、可维护性至关重要,更重要的优势是React生态中还有适用于Web端和原生APP的框架——React Native。
如果是单页应用数据相对不复杂的场景中,还是建议使用Vue作为框架来开发。(初级前端的个人观点...)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.