首页

最详细完整的flex弹性布局

前端达人

初了解

在学习弹性布局之前首先就要明白其概念
flex 就是flexible box的缩写,意为弹性布局,用来为盒装模型提供最大的灵活性
任何一个容器都可以指定为flex布局

.box{ display: flex; } 

行内元素当然也可以使用flex布局

.box{ display: inline-flex; } 

Webkit 内核的浏览器,必须加上-webkit前缀。

.box{ display: -webkit-flex; /* Safari */ display: flex; } 

注意:设为 Flex 布局以后,子元素的float、clear和vertical-align属性将失效。

基本概念

采用 Flex 布局的元素,称为 Flex 容器(flex container),简称"容器"。它的所有子元素自动成为容器成员,称为 Flex 项目(flex item),简称"项目"。
在这里插入图片描述
容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。主轴的开始位置(与边框的交叉点)叫做main start,结束位置叫做main end;交叉轴的开始位置叫做cross start,结束位置叫做cross end。
项目默认沿主轴排列。单个项目占据的主轴空间叫做main size,占据的交叉轴空间叫做cross size。
以上这些基础概念,请务必牢记,下面说属性时,不再重复说明!

属性

容器属性(container)

  • flex-direction
  • justify-content
  • align-items
  • flex-wrap
  • align-content
  • flex-flow

1.flex-direction

 flex items默认都是沿着main axis(主轴)从main start 开始往main end方向排布
    flex-direction决定了main axis的方向,有四个取值row(默认值)、row-reverse、column、column-reverse 
.box { flex-direction: row | row-reverse | column | column-reverse; } 

row(默认值):主轴为水平方向,起点在左端。
row-reverse:主轴为水平方向,起点在右端。
column:主轴为垂直方向,起点在上沿。
column-reverse:主轴为垂直方向,起点在下沿。

2.justify-content

 justify-content决定了flex item在main axis上的对齐方式

    flex-start(默认值):与main start对齐
    flex-end:与main end对齐
    center:居中
    space-between:flex items 之间的距离相等,与main start、main end两端对齐
    space-evently: flex items 之间的距离相等,flex items与main start 、main end 之间的距离等于flex items之间的距离
    space-around :flex items 之间的距离相等,flex items与main start 、main end 之间的距离等于flex items之间的距离的一半 

这个属性的目的主要就是为了排列main axis的item位置
在这里插入图片描述

在这里插入图片描述
当然,这些属性你可以自己尝试一下,这里就不再一一尝试了,但是注意,这些都是容器的属性,要写在容器的css中!

3.align-items

 决定flex items在cross axis上的对齐方式

    normal:在弹性布局中,效果和stretch一样
    stretch:前提是items不设置高度,当flex items 在cross axis 方向的size为auto时,会自动拉伸至填充flex container(或者换句话说:如果项目未设置高度或设为auto,将占满整个容器的高度。)
    flex-satrt:与cross start 对齐
    flex-end:与cross end 对齐
    center:居中对齐
    baseline:与基准线对齐 

4.flex-wrap

 决定了flex container 是单行还是多行
    nowrap(默认):单行
    warp:多行
    //这个比较少用
    wrap-reverse:多行(对比wrap,cross start 与cross end相反) 

默认情况下,项目都排在一条线(又称"轴线")上。flex-wrap属性定义,如果一条轴线排不下,如何换行。

5 align-content

 决定了多行flex items 在cross axis的对齐方式 用法与justify-content相似 一个是横轴。一个控制竖轴
    stretch(默认值):与align-items的stretch类似,当items有高度的时候,无效果
    flex-start:与cross start 对齐
    flex-end :与cross end 对齐
    center:居中对齐
    space-between:flex items 之间的距离相等,与cross start、cross end两端对齐
    space-evently: flex items 之间的距离相等,flex items与cross start 、cross end 之间的距离等于flex items之间的距离
    space-around :flex items 之间的距离相等,flex items与cross start 、cross end 之间的距离等于flex items之间的距离的一半 

6 flex-flow 是flex-direction与flex-wrap的简写

也就是说,当你使用这个属性的时候,你可以使用上述两个的属性值,例如:flex-flow: row wrap;(水平排列,多行显示)

flex 项目属性(item属性)

  • order
  • flex-grow
  • flex-shrink
  • flex-basis
  • align-self
  • flex

1 order

 order 决定flex items的排布顺序  (用的不多)
    可以设置为任意整数(正整数、负整数、0),值越小越排在前面
    默认值为0 

这个属性了解即可,说实话没怎么用过

2 align-self

 可以通过align-self 覆盖flex container 设置的align-items
    auto(默认值):遵从flex container的align-items设置
    stretch、flex-start、flex-end、center、baseline效果与align-items一致 

相当于继承父元素的align-items属性,如果没有父元素,则等同于stretch。

3 flex-grow

 决定了flex items如何扩展
    可以设置为任意非父数字(小数,整数 0),默认为0
    当flex container 在main axis方向上有剩余得size时,flex-grow属性才会有效

    如果所有flex items 的flex-grow 综合sum不超过1,这直接乘以剩余size就是扩展大小、
    如果超过1 扩展size=剩余size*flex-grow/sum 

flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。

4 flex-shrink

flex-shrink (shrink 缩小,收缩)与flex-grow相似,一个扩展,一个伸缩 
    可以设置为任意非父数字(小数,整数 0),默认为1
    当flex items在main axis 方向上超过了flex container 的size flex-shrink属性才会生效、
    如果所有flex items 的flex-shrink 总和sum超过1,每个flex item 收缩的size为:
        flex item 超出flex container 的size*收缩比例/每个flex items 的收缩比例之和
    如果sum不超过1,每个flex item 收缩的size为:
        size = 超出的size * flex-shrink值
    flex items收缩后的最终size不能小于min-width\min-height 

有扩大自然就会有缩小,flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。具体的可以自己动手尝试一下哦,最后将会给出一个骰子布局的案例!

5 flex-basis

用来设置flex items 在 main axis方向上的base size
    默认为auto,可以设置具体的宽度数值

    决定flex items最终base size 的因素,优先级从高到低
        max-width\max-height\min-width\min-height
        flex-basis
        width\height
        内容本身的size 

flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目(item)的本来大小。也可以设置跟width,height一样的宽高,表示item将占据固定的空间!

6 flex

flex 是flex-grow || flex-shink||flex-basis的简写
可以指定1 2 3个值 依次按照上述顺序!默认值为 0 1 auto 
.item { flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ] } 

注意:

  1. 该属性的默认值为 0 1 auto(注意顺序),后两个属性可选
  2. 该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。
  3. 如果需要这三个属性的时候,建议使用flex,而不是单独的三个分离的属性,因为浏览器会推算相关值

骰子布局实践

光说不练假把式,手撕代码真功夫!
下面利用flex写了几个骰子布局,可以参考一下!

 
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <style type="text/css">
            #container{
                background-color: #CCCCCC;
                height: 600px;
                width: 500px;
                /* flex */
                display: flex;
                justify-content: space-evenly;
                align-items: center;
            }
            .item{
                background-color: yellow;
                width: 100px;
                height: 100px;

            }
            /* 单点 */
            .one{
                /* 对点使用flex布局 */
                display: flex;
                justify-content: center;
                align-items: center;
            }
            /* 点 */
            .item-one{
                display: block;
                height: 20px;
                width: 20px;
                background-color: #1890FF;
                border-radius: 50%;
            }
            /* 三点 */
            .two{

                display: flex;
                justify-content: space-between;
            }
            .two span{
                margin: 2px;
                display: block;
                height: 20px;
                width: 20px;
                border-radius: 50%;
                background-color: #1890FF;
            }
            .two2{
                align-self: center;
            }
            .two3{
                align-self: flex-end;
            }
            /* 五点 */
            .three{
                display: flex;
                justify-content: space-around;
            }
            .three span{
                display: block;
                height: 20px;
                width: 20px;
                border-radius: 50%;
                background-color: #1890FF;
            }
            #three_one, #three_three{
                padding: 2px;
                display: flex;
                flex-direction: column;
                justify-content: space-between;
            }
            #three_two{
                display: flex;
                flex-direction: column;
                justify-content: center;
            }
            /* 六点 */
            .four{
                display: flex;
                justify-content: space-around;
            }
            .four span{
                display: block;
                height: 20px;
                width: 20px;
                border-radius: 50%;
                background-color: #1890FF;
            }
            #four1,#four2{
                padding: 2px;
                display: flex;
                flex-direction: column;
                justify-content: space-between;
            }
        </style>
    </head>
    <body>
        <div id="container">
            <!-- 一个点居中 -->
            <div class="item one">
                <span class="item-one"></span>
            </div>
            <!-- 三点 -->
            <div class="item two">
                <span class="two1"></span>
                <span class="two2"></span>
                <span class="two3"></span>
            </div>
            <!-- 五点 -->
            <div class="item three">
                <div id="three_one">
                    <span></span>
                    <span></span>
                </div>
                <div id="three_two">
                    <span></span>
                </div>
                <div id="three_three">
                    <span></span>
                    <span></span>
                </div>
            </div>
            <!-- 六点 -->
            <div class="item four">
                <div id="four1">
                    <span></span>
                    <span></span>
                    <span></span>
                </div>
                <div id="four2">
                    <span></span>
                    <span></span>
                    <span></span>
                </div>
            </div>

        </div>
    </body>
</html>

测试结果

在这里插入图片描述



远程桌面访问阿里云服务器的方法

seo达人

我用的是Windows版本的阿里云服务器。

首先,打开服务器,找到已经创建好的服务器实例并点击。

之后会跳转到实例页面,点击右侧的“管理”

然后配置安全组。安全组中就是设置哪些IP可以访问我们的服务器。

然后在安全组配置规则。

添加新规则。

想要让Windows电脑远程链接服务器需要开放3389端口。不然就无法用自己的电脑远程链接服务器了。

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

配置snmpd for windows

seo达人

1.安装


Windows 安装请参考


默认安装为c:/usr/ 。可以执行如下文件,以便启动snmp agent:


snmpd

会提示需要配置snmpd.conf。


No log handling enabled - turning on stderr logging

Warning: no access control information configured.

 It's unlikely this agent can serve any useful purpose in this state.

 Run "snmpconf -g basic_setup" to help you configure the snmpd.conf file for this agent.

NET-SNMP version 5.5

不必使用提示中的命令,因为此命令引用了perl,但是perl的对应模块无法跑起来。看来perl要完蛋的传说并不是空穴来风。


snmpd.conf可以自己创建到/usr/etc/snmp/snmpd.conf内。


2.修改配置文件


配置之前的说明:我在网上看到的所有配置都是com2sec,group,access这三个配置,但是从默认的snmp.conf文件中有一段话:




没必要使用 com2sec/group/access配置,使用ro(w)user,ro(w)community结合合适的views,就可以覆盖大多数需求了。


2.1  配置监听地址

snmpd默认监听本地IP的UDP161端口,等待snmp请求


agentAddress udp:161

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

2.2  配置视图(view)

格式:view viewName type oid[mask]


参数说明:  


   

   viewName : view名称

   type : 有两个值:include和exclude(包括或者排除)

   oid:可以访问的oid(mib子树)

   [mask]:对oid的掩码 (可选参数)

案例:


view systemonly included .1.3.6.1.2.1

2.3  配置共同体(community)

格式:ro(w)community communityName source


参数说明:


rocommunity定义一个只读权限的共同体,rwcommunity定义一个读写权限的共同体

rocommunity6或   rwcommunity6表示监听IPv6。

communityName:共同体名称

source:可以访问的IP地址范围,默认为”default”,即任何IP都能访问。


               可选参数:-V viewName  限制共同体只能访问viewName下的节点


实际配置文件

agentAddress udp:161

view systemonly included .1.3.6.1.2.1

rocommunity public default

3. 测试

修改完配置之后,重启snmpd:


snmpd

连接测试:(该节点表示获取主机内存大小)


snmpwalk -v 1 -c public localhost .1.3.6.1.2.1.1.1

SNMPv2-MIB::sysDescr.0 = STRING: Windows DESKTOP-70OA76Q 6.2.9200   Professional

得到类似以上的结果,就说明snmpd配置成功。


配置MIB库

Windows版本的snmp自带有很多MIB,位于C:\usr\share\snmp\mibs


因此可以对OID和MIB name做互相转换:


snmptranslate -On SNMPv2-MIB::sysDescr.0

.1.3.6.1.2.1.1.1.0


snmptranslate .1.3.6.1.2.1.1.1.0

SNMPv2-MIB::sysDescr.0

添加自己的mib

之前的笔记有提到自己创建的一个定制mib文件


https://github.com/1000copy/tbit-guard-snmp/blob/master/tbit.mib

可以把它加入到C:\usr\share\snmp\mibs目录内,文件名无所谓。然后为snmptranslate加入选项 -mALL ,表示加载所有在此目录内的mib文件:


snmptranslate -m ALL .1.3.6.1.4.1.66666

你应该看到如下消息,表明mib加载成功:


tbitinc::tbit

详细的说明如下:


-m MIBLIST

     指定一个用冒号分隔的MIB模块列表(不是文件),以便为这个应用程序加载。 这将覆盖(或增加)环境变量MIBS、snmp.conf的内容。指令mibs,以及硬编码到Net-SNMP库中的MIBs列表。如果MIBLIST有一个前导的'-'或'+'字符,那么所列出的MIB模块将被加载到默认列表之外,分别位于该列表之前或之后。

     否则,将加载指定的MIB,而不是这个默认列表。特殊关键字ALL用于加载MIB目录搜索列表中的所有MIB模块。 每一个名字不以". "开头的文件都会被解析为是一个

     MIB文件。

-M DIRLIST

     指定一个以冒号分隔的目录列表来搜索MIB。 这将覆盖(或增强)环境变量MIBDIRS和snmp.conf指令mibdirs。

     以及硬编码到 Net-SNMP 库中的默认目录 (/usr/share/snmp/mibs)。


     如果DIRLIST有一个前导的'-'或'+'字符,那么给定的目录会被添加到默认列表中,在这个列表的目录之前或之后进行搜索。

     分别进行搜索。 否则,将搜索指定的目录,而不是这个默认列表。


     请注意,列表中出现在后面的目录要比前面的目录优先。 要避免搜索任何MIB目录,请将MIBDIRS环境中的

     变量为空字符串("")。


     请注意,使用-m选项或mibs配置指令指定的MIB将从-M选项(或等价物)列出的目录中加载。

     mibfile指令采用的是指定MIB文件的完整路径,所以不需要在MIB目录搜索列表中出现。

10分钟彻底搞懂单页面应用路由

seo达人

单页面应用特征

假设: 在一个 web 页面中,有1个按钮,点击可跳转到站内其他页面。


多页面应用: 点击按钮,会从新加载一个html资源,刷新整个页面;


单页面应用: 点击按钮,没有新的html请求,只发生局部刷新,能营造出一种接近原生的体验,如丝般顺滑。


SPA 单页面应用为什么可以几乎无刷新呢?因为它的SP——single-page。在第一次进入应用时,即返回了唯一的html页面和它的公共静态资源,后续的所谓“跳转”,都不再从服务端拿html文件,只是DOM的替换操作,是模(jia)拟(zhuang)的。


那么js又是怎么捕捉到组件切换的时机,并且无刷新变更浏览器url呢?靠hash和HTML5History。


hash 路由

特征

类似www.xiaoming.html#bar 就是哈希路由,当 # 后面的哈希值发生变化时,不会向服务器请求数据,可以通过 hashchange 事件来监听到 URL 的变化,从而进行DOM操作来模拟页面跳转

不需要服务端配合

对 SEO 不友好

原理

hash


HTML5History 路由

特征

History 模式是 HTML5 新推出的功能,比之 hash 路由的方式直观,长成类似这个样子www.xiaoming.html/bar ,模拟页面跳转是通过 history.pushState(state, title, url) 来更新浏览器路由,路由变化时监听 popstate 事件来操作DOM

需要后端配合,进行重定向

对 SEO 相对友好

原理

Html5 History


vue-router 源码解读

以 Vue 的路由vue-router为例,我们一起来撸一把它的源码。


Tips:因为,本篇的重点在于讲解单页面路由的两种模式,所以,下面只列举了一些关键代码,主要讲解:


注册插件

VueRouter的构造函数,区分路由模式

全局注册组件

hash / HTML5History模式的 push 和监听方法

transitionTo 方法

注册插件

首先,作为一个插件,要有暴露一个install方法的自觉,给Vue爸爸去 use。


源码的install.js文件中,定义了注册安装插件的方法install,给每个组件的钩子函数混入方法,并在beforeCreate钩子执行时初始化路由:


Vue.mixin({

 beforeCreate () {

   if (isDef(this.$options.router)) {

     this._routerRoot = this

     this._router = this.$options.router

     this._router.init(this)

     Vue.util.defineReactive(this, '_route', this._router.history.current)

   } else {

     this._routerRoot = (this.$parent && this.$parent._routerRoot) || this

   }

   registerInstance(this, this)

 },

 // 全文中以...来表示省略的方法

 ...

});

区分mode

然后,我们从index.js找到整个插件的基类 VueRouter,不难看出,它是在constructor中,根据不同mode 采用不同路由实例的。


...

import {install} from './install';

import {HashHistory} from './history/hash';

import {HTML5History} from './history/html5';

...

export default class VueRouter {

 static install: () => void;

 constructor (options: RouterOptions = {}) {

   if (this.fallback) {

     mode = 'hash'

   }

   if (!inBrowser) {

     mode = 'abstract'

   }

   this.mode = mode

         

   switch (mode) {

     case 'history':

       this.history = new HTML5History(this, options.base)

       break

     case 'hash':

       this.history = new HashHistory(this, options.base, this.fallback)

       break

    case 'abstract':

       this.history = new AbstractHistory(this, options.base)

       break

    default:

     if (process.env.NODE_ENV !== 'production') {

       assert(false, `invalid mode: ${mode}`)

     }

   }

 }

}

全局注册router-link组件

这个时候,我们也许会问:使用 vue-router 时, 常见的<router-link/>、 <router-view/>又是在哪里引入的呢?


回到install.js文件,它引入并全局注册了 router-view、router-link组件:


import View from './components/view';

import Link from './components/link';

...

Vue.component('RouterView', View);

Vue.component('RouterLink', Link);

在 ./components/link.js 中,<router-link/>组件上默认绑定了click事件,点击触发handler方法进行相应的路由操作。


const handler = e => {

 if (guardEvent(e)) {

   if (this.replace) {

     router.replace(location, noop)

   } else {

     router.push(location, noop)

   }

}

};

就像最开始提到的,VueRouter构造函数中对不同mode初始化了不同模式的 History 实例,因而router.replace、router.push的方式也不尽相同。接下来,我们分别扒拉下这两个模式的源码。


hash模式

history/hash.js 文件中,定义了HashHistory 类,这货继承自 history/base.js 的 History 基类。


它的prototype上定义了push方法:在支持 HTML5History 模式的浏览器环境中(supportsPushState为 true),调用history.pushState来改变浏览器地址;其他浏览器环境中,则会直接用location.hash = path 来替换成新的 hash 地址。


其实最开始读到这里是有些疑问的,既然已经是 hash 模式为何还要判断supportsPushState?是为了支持scrollBehavior,history.pushState可以传参key过去,这样每个url历史都有一个key,用 key 保存了每个路由的位置信息。


同时,原型上绑定的setupListeners 方法,负责监听 hash 变更的时机:在支持 HTML5History 模式的浏览器环境中,监听popstate事件;而其他浏览器中,则监听hashchange。监听到变化后,触发handleRoutingEvent 方法,调用父类的transitionTo跳转逻辑,进行 DOM 的替换操作。


import { pushState, replaceState, supportsPushState } from '../util/push-state'

...

export class HashHistory extends History {

 setupListeners () {

   ...

   const handleRoutingEvent = () => {

       const current = this.current

       if (!ensureSlash()) {

         return

       }

       // transitionTo调用的父类History下的跳转方法,跳转后路径会进行hash化

       this.transitionTo(getHash(), route => {

         if (supportsScroll) {

           handleScroll(this.router, route, current, true)

         }

         if (!supportsPushState) {

           replaceHash(route.fullPath)

         }

       })

     }

     const eventType = supportsPushState ? 'popstate' : 'hashchange'

     window.addEventListener(

       eventType,

       handleRoutingEvent

     )

     this.listeners.push(() => {

       window.removeEventListener(eventType, handleRoutingEvent)

     })

 }

 

 push (location: RawLocation, onComplete?: Function, onAbort?: Function) {

   const { current: fromRoute } = this

   this.transitionTo(

     location,

     route => {

       pushHash(route.fullPath)

       handleScroll(this.router, route, fromRoute, false)

       onComplete && onComplete(route)

     },

     onAbort

   )

 }

}

...


// 处理传入path成hash形式的URL

function getUrl (path) {

 const href = window.location.href

 const i = href.indexOf('#')

 const base = i >= 0 ? href.slice(0, i) : href

 return `${base}#${path}`

}

...


// 替换hash

function pushHash (path) {

 if (supportsPushState) {

   pushState(getUrl(path))

 } else {

   window.location.hash = path

 }

}


// util/push-state.js文件中的方法

export const supportsPushState =

 inBrowser &&

 (function () {

   const ua = window.navigator.userAgent


   if (

     (ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&

     ua.indexOf('Mobile Safari') !== -1 &&

     ua.indexOf('Chrome') === -1 &&

     ua.indexOf('Windows Phone') === -1

   ) {

     return false

   }

   return window.history && typeof window.history.pushState === 'function'

 })()

HTML5History模式

类似的,HTML5History 类定义在 history/html5.js 中。


定义push原型方法,调用history.pusheState修改浏览器的路径。


与此同时,原型setupListeners 方法对popstate进行了事件监听,适时做 DOM 替换。


import {pushState, replaceState, supportsPushState} from '../util/push-state';

...

export class HTML5History extends History {


 setupListeners () {


   const handleRoutingEvent = () => {

   const current = this.current;

   const location = getLocation(this.base);

   if (this.current === START && location === this._startLocation) {

     return

   }


   this.transitionTo(location, route => {

     if (supportsScroll) {

       handleScroll(router, route, current, true)

     }

   })

   }

   window.addEventListener('popstate', handleRoutingEvent)

   this.listeners.push(() => {

     window.removeEventListener('popstate', handleRoutingEvent)

   })

 }

 push (location: RawLocation, onComplete?: Function, onAbort?: Function) {

   const { current: fromRoute } = this

   this.transitionTo(location, route => {

     pushState(cleanPath(this.base + route.fullPath))

     handleScroll(this.router, route, fromRoute, false)

     onComplete && onComplete(route)

   }, onAbort)

 }

}


...


// util/push-state.js文件中的方法

export function pushState (url?: string, replace?: boolean) {

 saveScrollPosition()

 const history = window.history

 try {

   if (replace) {

     const stateCopy = extend({}, history.state)

     stateCopy.key = getStateKey()

     history.replaceState(stateCopy, '', url)

   } else {

     history.pushState({ key: setStateKey(genStateKey()) }, '', url)

   }

 } catch (e) {

   window.location[replace ? 'replace' : 'assign'](url)

 }

}

transitionTo 处理路由变更逻辑

上面提到的两种路由模式,都在监听时触发了this.transitionTo,这到底是个啥呢?它其实是定义在 history/base.js 基类上的原型方法,用来处理路由的变更逻辑。

先通过const route = this.router.match(location, this.current)对传入的值与当前值进行对比,返回相应的路由对象;接着判断新路由是否与当前路由相同,相同的话直接返回;不相同,则在this.confirmTransition中执行回调更新路由对象,并对视图相关DOM进行替换操作。


export class History {

...

transitionTo (

   location: RawLocation,

   onComplete?: Function,

   onAbort?: Function

 ) {

   const route = this.router.match(location, this.current)

   this.confirmTransition(

     route,

     () => {

       const prev = this.current

       this.updateRoute(route)

       onComplete && onComplete(route)

       this.ensureURL()

       this.router.afterHooks.forEach(hook => {

         hook && hook(route, prev)

       })


       if (!this.ready) {

         this.ready = true

         this.readyCbs.forEach(cb => {

           cb(route)

         })

       }

     },

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

Vue.js教程-目录

前端达人

简介

  • 本目录作为Vue教程的首页,所以会持续更新。
  • 如果某篇章节中有错误的地方,希望大家能够指出来,我会更正,私信和评论里说都可以,不懂的地方也可以说,如果我也不会那就请教一下大佬们吧,毕竟我对前端的东西也不是特别了解,多多包涵!嘿嘿。

章节列表

章节名称 地址
Vue.js教程-安装和HelloWorld https://coderhqf.blog.csdn.net/article/details/107574556
Vue.js教程-Vue项目的目录结构和.vue文件的构成 https://coderhqf.blog.csdn.net/article/details/107621070
Vue.js教程-Vue基本指令 https://coderhqf.blog.csdn.net/article/details/107677588
Vue.js教程-组件化开发 https://coderhqf.blog.csdn.net/article/details/107783664

Vue简介

  • Vue官网
  • Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。
  • Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
  • Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

Vue特点

  • 易用:在有HTML CSS JavaScript的基础上,快速上手。
  • 灵活:简单小巧的核心,渐进式技术栈,足以应付任何规模的应用。
  • 性能:20kb min+gzip 运行大小、超快虚拟 DOM 、最省心的优化。

Vue中数据观测的实现

  • Vue.js利用了ES5的Object.defineProperty方法,直接将原生数据对象的属性改造为getter和setter,在这两个函数内部实现依赖的收集和触发,而且完美支持嵌套的对象结构。对于数组,则通过包裹数组的可变方法(比如push)来监听数组的变化。这使得操作Vue.js的数据和操作原生对象几乎没有差别[注:在添加/删除属性,或是修改数组特定位置元素时,需要调用特定的函数,如obj.$add(key, value)才能触发更新。这是受ES5的语言特性所限。],数据操作的逻辑更为清晰流畅,和第三方数据同步方案的整合也更为方便。

Vue项目打包

  • 在构建大型应用时,推荐使用Webpack+vue-loader这个组合以使针对组件的开发更。
  • 在后面的章节中会细说怎么打包并部署到服务器上,也会讲怎么白嫖阿里云(学生版),好像有大佬写过这个文章,大家搜一下也行,最重要的还是开发。

Vue的组件化开发

  • Vue最主要的是组件化开发,因为是单页面,也就是在一个页面中渲染出多个页面的效果,这个特点能够让非常多的组件在不同的项目中重复使用。
  • Vue中的组件基于Web components进行了上层功能的实现,例如数据绑定、动画系统等。

Vue与后端的数据交互:axios

  • 传统的一般都用Ajax,但如果请求有先后关系的话就容易产生回调地狱,套娃套的吧。
  • Vue2之后就推荐使用axios了,等写到前后端交互的时候再讲这个就行。

相关说明

  • Vue参考了AngularJS、KnockoutJS、Ractive.js、Rivets.js,可以是把他们的缺点都优化成了自己的优点,参考过程中不但去其糟粕,还加入了自己的特性,但目前也只有国内用Vue的比较多,毕竟社区小,资源少,但以后应该会是潮流,因为开发快好上手。
  • 其实Vue相对来说是非常好上手的,因为它只专注于视图层。如果只是要用的话,其实对原理也不用太纠结,但既然要全栈,何不贯彻到底,我也是在学习中,如果想正规学习的话,我比较推荐coderwhy-王红君老师的课,他讲的挺好的,有些原理讲的也是很清楚的,渠道嘛,B站大学、腾讯课堂啥的都有,还有他的微博,大家可以去网上找。
  • 再强调一遍,如果发现不对的地方请联系我,因为不想误人子弟,毕竟这是自己的总结,也不想以后自己还用着错误的东西,嘿嘿嘿。。。
  • Vue作为现在国内最潮流的前端框架,也逐渐成为后端开发人员需要学的新知识了,我看现在很多后端岗位的面试里都会提到这个前端框架,所以大家学一下是不亏的。
  • 在CSDN杂志中有一篇文章:Vue.js:轻量的前端组件化方案,如果大家有兴趣就看看吧。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_45062103/article/details/107763788

JavaScript中的for循环

seo达人

JavaScript 语言中的 for 循环用于多次执行代码块,它是 JavaScript 中最常用的一个循环工具,还可用于数组的遍历循环等。


我们为什么要使用 for 循环呢?打个比方,例如我们想要控制台输出1到1000之间的所有数字,如果单写输出语句,要写1000句代码,但是如果使用 for 循环,几句代码就能实现。总之,使用 for 循环能够让我们写代码更方便快捷(当然啦,否则要它干嘛)。


for 循环语法

语法如下所示:


for(变量初始化; 条件表达式; 变量更新) {

   // 条件表达式为true时执行的语句块

}

变量初始化,表示代码块开始前执行。

条件表达式,定义运行循环代码块的条件。

变量更新,在循环代码块每次被执行之后再执行。

示例:

例如我们在一个HTML文件中,编写如下代码,实现计算1到100的总和:


<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8">

<title>JS_侠课岛(9xkd.com)</title>

</head>

<body>

<script>

 var result = 0;

 for(var i = 1; i <= 100; i++) {

   result = result + i;

 }

 alert(result);

</script>

</body>  

</html>

在浏览器中打开这个文件,会弹出一个弹出层,弹出层中显示的是1到100的总和:



上述代码中,我们声明了一个变量 result 并给它赋值为 0,表示初始的总和为 0 。


然后在 for 循环中三个语句:


变量初始化 i = 1,表示从 1 开始计算。

条件表达式 i <= 100,表示只要 i 小于等于 100 循环就会一直执行,当 i 大于 100 循环会停止。

变量更新 i++,之前我们学运算符的时候学过,这是递增运算符 ++,表示为其操作数增加 1。

此时我们可以一点点来看这个 for 循环:


第一次循环: result = 0 + 1   // 此时result值为0,  i的值为1

第二次循环: result = 1 + 2   // 此时result值为0+1,i的值为2

第三次循环: result = 3 + 3   // 此时result值为1+2,i的值为3

第四次循环: result = 6 + 4   // 此时result值为3+3,i的值为4

第五次循环: result = 10 + 5  // 此时result值为6+4,i的值为5

...

我们只需要搞清楚 for 循环中的执行原理,不需要手动来计算求和,只要写好代码,执行代码后计算机会很快会告诉我们1到 100 的总和。


再补充一下,上述代码中result = result + i,我们也可以写成 result += i,这是我们之前学过的加赋值运算符,还记得吗?


示例:

再来看一个例子,例如我们可以使用 for 循环来实现数组遍历,首先定义一个数组 lst:


var lst = ["a", "b", "c", "d", "e"];

在写 for 循环时,首先就是要搞清楚小括号里面的三个语句,因为我们可以通过数组中元素的下标索引来获取元素的值,而数组的索引又是从 0 开始,所以变量初始化可以设置为i = 0。第二个条件表达式,因为数组中最后一个索引为 lst.length - 1,所以只要小于等于 lst.length - 1,循环就会一直执行。而i <= lst.length - 1 就相当于 i<lst.length。第三个变量更新,当循环每循环一次,索引值就加一,所以为 i++。


所以循环可以像下面这样写:


for(i = 0; i<lst.length; i++){

   console.log(lst[i]);  // 输出数组中的元素值,从索引为0的值开始输出,每次加1,一直到lst.length-1

}

输出:


a

b

c

d

e

其实遍历数组还有一种更好的方法,就是使用 for...in 循环语句来遍历数组。


for...in 循环

for...in 循环主要用于遍历数组或对象属性,对数组或对象的属性进行循环操作。for...in 循环中的代码每执行一次,就会对数组的元素或者对象的属性进行一次操作。


语法如下:


for (变量 in 对象) {

   // 代码块

}

for 循环括号内的变量是用来指定变量,指定的可以是数组对象或者是对象属性。


示例:

使用 for...in 循环遍历我们定义好的 lst 数组:


var lst = ["a", "b", "c", "d", "e"];

for(var l in lst){

   console.log(lst[l]);

}

输出:


a

b

c

d

e

除了数组,for...in 循环还可以遍历对象,例如我们遍历 侠侠 的个人基本信息:


var object = {

   姓名:'侠侠',

   年龄:'22',

   性别:'男',

   出生日期:'1997-08-05',

   职业:'程序员',

   特长:'跳舞'

}


for(var i in object) {

   console.log(i + ":" + object[i]);

}

输出:


姓名: 侠侠

年龄: 22

性别: 男

出生日期: 1997-08-05

职业:程序员

特长:跳舞

动手小练习

请自定义一个长度为7的数组,然后通过 for 循环将数组中的元素遍历出来。

求和:1~100的奇数和。

求和:1~100的偶数和。

使用对象定义一个人的个人信息(包括姓名、性别、年龄、出生日期、兴趣爱好、职业、特长等),然后使用 for...in 循环将这些信息遍历输出。

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

封装element-ui表格

seo达人

表格需求

一般管理系统对表格会有以下需求


可以分页(需要有分页条)

可以多选(表格带复选框)

顶部需要加一些操作按钮(新增,删除等等)

表格每行行尾有操作按钮

表格行可以编辑

如下图为一个示例表格




如果我们直接使用element-ui提供的组件的话,那么开发一个这样的表格就需要使用到以下内容


需要使用表格的插槽功能,开发每一行的按钮

需要通过样式调整顶部按钮,表格,分页条的布局样式

需要监听分页的事件然后去刷新表格数据

顶部按钮或操作按钮如果需要获取表格数据,需要调用表格提供的api

对于有行编辑的需求,还需要通过插槽去渲染行编辑的内容,同时要控制行编辑的开关

不仅仅开发表格比较麻烦,而且还要考虑团队协作,如果每个人实现表格的方式存在差别,那么可能后期的维护成本也会变得很高。那怎么办呢?


项目安装

安装插件

在使用element-ui的项目中,可以通过以下命令进行安装


npm install vue-elementui-table -S

在项目中使用

在main.js中添加以下代码


import ZjTable from 'vue-element-table'


Vue.use(ZjTable)

然后即可像下文中的使用方式进行使用


表格配置

为了满足团队快速开发的需要,小编对上面提出来的需求进行了封装,然后使用的时候,开发人员只需要配置一些JSON便可以完成以上功能的开发。


基础配置

一个基础的表格包含了数据和列信息,那么如何用封装的表格去配置呢?


<template>

 <zj-table

   :columns="columns"

   :data="data"

   :pagination="false"

 />

</template>

<script>

export default {

 data() {

   return {

     // 表格的列信息, 数组每一项代表一个字段,可以使用element 列属性的所有属性,以下仅为示例

     columns: Object.freeze([

       {

         // 表头显示的文字

         label: '姓名',

         // 对应数据里面的字段

         prop: 'name'

       },

       {

         label: '性别',

         prop: 'sex',

         // 格式化表格,与element-ui 的表格属性相同

         formatter(row, column, cellValue) {

           return cellValue === 1 ? '男' : '女'

         }

       },

       {

         label: '年龄',

         prop: 'age'

       }

     ]),

     data: [

       {

         name: '子君',

         sex: 1,

         age: 18

       }

     ]

   }

 }

}

</script>

通过上面的配置,就可以完成一个基础表格的开发,完整代码见 https://github.com/snowzijun/vue-element-table/blob/master/example/views/demo/base.vue,效果如下图所示




表格默认会显示复选框,也可以通过配置selectable属性来关闭掉


添加分页

简单的表格用封装之后的或未封装的开发工作量区别并不大,我们继续为表格添加上分页


<template>

   <!--

   current-page.sync 表示页码, 添加上 .sync 在页码发生变化时自动同步页码

   page-size.sync 每页条数

   total  总条数

   height="auto" 配置height:auto, 表格高度会根据内容自动调整,如果不指定,表格将保持充满父容器,同时表头会固定,不跟随滚动条滚动

   @page-change 无论pageSize currentPage 哪一个变化,都会触发这个事件

 -->

 <zj-table

   v-loading="loading"

   :columns="columns"

   :data="data"

   :current-page.sync="currentPage"

   :page-size.sync="pageSize"

   :total="total"

   height="auto"

   @page-change="$_handlePageChange"

 />

</template>

<script>

export default {

 data() {

   return {

     columns: Object.freeze([

       // 列字段与上例一样,此处省略

     ]),

     data: [],

     // 当前页码

     currentPage: 1,

     // 每页条数

     pageSize: 10,

     // 总条数

     total: 0,

     // 是否显示loading

     loading: false

   }

 },

 created() {

   this.loadData()

 },

 methods: {

   // 加载表格数据

   loadData() {

     this.loading = true

     setTimeout(() => {

       // 假设总条数是40条

       this.total = 40

       const { currentPage, pageSize } = this

       // 模拟数据请求获取数据

       this.data = new Array(pageSize).fill({}).map((item, index) => {

         return {

           name: `子君${currentPage + (index + 1) * 10}`,

           sex: Math.random() > 0.5 ? 1 : 0,

           age: Math.floor(Math.random() * 100)

         }

       })

       this.loading = false

     }, 1000)

   },

   $_handlePageChange() {

     // 因为上面设置属性指定了.sync,所以这两个属性会自动变化

     console.log(this.pageSize, this.currentPage)

     // 分页发生变化,重新请求数据

     this.loadData()

   }

 }

}

</script>

完整代码请参考 https://github.com/snowzijun/vue-element-table/blob/master/example/views/demo/pagination.vue


通过封装,表格将自带分页功能,通过上面代码,实现效果如下所示,是不是变得简单了一些。接下来我们继续给表格添加按钮




添加顶部按钮

表格上面可能会有新增,删除等等按钮,怎么办呢,接下来我们继续通过配置去添加按钮


<template>

 <zj-table

   :buttons="buttons"

 />

</template>

<script>

export default {

 data() {

   return {

     buttons: Object.freeze([

       {

         // id 必须有而且是在当前按钮数组里面是唯一的

         id: 'add',

         text: '新增',

         type: 'primary',

         icon: 'el-icon-circle-plus',

         click: this.$_handleAdd

       },

       {

         id: 'delete',

         text: '删除',

         // rows 是表格选中的行,如果没有选中行,则禁用删除按钮, disabled可以是一个boolean值或者函数

         disabled: rows => !rows.length,

         click: this.$_handleRemove

       },

       {

         id: 'auth',

         text: '这个按钮根据权限显示',

         // 可以通过返回 true/false来控制按钮是否显示

         before: (/** rows */) => {

           return true

         }

       },

       // 可以配置下拉按钮哦

       {

         id: 'dropdown',

         text: '下拉按钮',

         children: [

           {

             id: 'moveUp',

             text: '上移',

             icon: 'el-icon-arrow-up',

             click: () => {

               console.log('上移')

             }

           },

           {

             id: 'moveDown',

             text: '下移',

             icon: 'el-icon-arrow-down',

             disabled: rows => !rows.length,

             click: () => {

               console.log('下移')

             }

           }

         ]

       }

     ])

   }

 },

 created() {},

 methods: {

   // 新增

   $_handleAdd() {

     this.$alert('点击了新增按钮')

   },

   // 顶部按钮会自动将表格所选的行传出来

   $_handleRemove(rows) {

     const ids = rows.map(({ id }) => id)

     this.$alert(`要删除的行id为${ids.join(',')}`)

   },

   // 关注作者公众号

   $_handleFollowAuthor() {}

 }

}

</script>

表格顶部可以添加普通的按钮,也可以添加下拉按钮,同时还可以通过before来配置按钮是否显示,disabled来配置按钮是否禁用,上面完整代码见 https://github.com/snowzijun/vue-element-table/blob/master/example/views/demo/button.vue


通过上面的代码就可以配置出下面的表格,是不是很简单呢?




表格顶部可以有按钮,行尾也是可以添加按钮的,一起来看看


行操作按钮

一般我们会将一些单行操作的按钮放在行尾,比如编辑,下载等按钮,那如何给行尾配置按钮呢?


<template>

 <zj-table

   :columns="columns"

 />

</template>

<script>

export default {

 data() {

   return {

     columns: Object.freeze([

       {

         // 可以指定列的宽度,与element-ui原生用法一致

         width: 220,

         label: '姓名',

         prop: 'name'

       },

       // 行编辑按钮,在表格末尾出现,自动锁定右侧

       {

         width: 180,

         label: '操作',

         // 通过 actions 指定行尾按钮

         actions: [

           {

             id: 'follow',

             text: '关注作者',

             click: this.$_handleFollowAuthor

           },

           {

             id: 'edit',

             text: '编辑',

             // 可以通过before控制按钮是否显示,比如下面年龄四十岁的才会显示编辑按钮

             before(row) {

               return row.age < 40

             },

             click: this.$_handleEdit

           },

           {

             id: 'delete',

             text: '删除',

             icon: 'el-icon-delete',

             disabled(row) {

               return row.sex === 0

             },

             // 为了拿到this,这里需要用箭头函数

             click: () => {

               this.$alert('女生被禁止删除了')

             }

           }

         ]

       }

     ])

   }

 },

 methods: {

   // 关注作者公众号

   $_handleFollowAuthor() {

           console.log('微信搜索【前端有的玩】,这是对小编最大的支持')

   },

   /**

    * row 这一行的数据

    */

   $_handleEdit(row, column) {

     this.$alert(`点击了姓名为【${row.name}】的行上的按钮`)

   }

 }

}

</script>

行操作按钮会被冻结到表格最右侧,不会跟随滚动条滚动而滚动,上面完整代码见, https://github.com/snowzijun/vue-element-table/blob/master/example/views/demo/button.vue


通过上面的代码就可以完成以下效果




最后再来一起看看行编辑


行编辑

比如上例,我希望点击行尾的编辑按钮的时候,可以直接在行上面编辑用户的姓名与性别,如何配置呢?


<template>

 <zj-table

   ref="table"

   :columns="columns"

   :data="data"

 />

</template>

<script>

export default {

 data() {

   return {

     columns: Object.freeze([

       {

         label: '姓名',

         prop: 'name',

         editable: true,

         field: {

           componentType: 'input',

           rules: [

             {

               required: true,

               message: '请输入姓名'

             }

           ]

         }

       },

       {

         label: '性别',

         prop: 'sex',

         // 格式化表格,与element-ui 的表格属性相同

         formatter(row, column, cellValue) {

           return cellValue === '1' ? '男' : '女'

         },

         editable: true,

         field: {

           componentType: 'select',

           options: [

             {

               label: '男',

               value: '1'

             },

             {

               label: '女',

               value: '0'

             }

           ]

         }

       },

       {

         label: '年龄',

         prop: 'age',

         editable: true,

         field: {

           componentType: 'number'

         }

       },

       {

         label: '操作',

         actions: [

           {

             id: 'edit',

             text: '编辑',

             // 如果当前行启用了编辑,则不显示编辑按钮

             before: row => {

               return !this.editIds.includes(row.id)

             },

             click: this.$_handleEdit

           },

           {

             id: 'save',

             text: '保存',

             // 如果当前行启用了编辑,则显示保存按钮

             before: row => {

               return this.editIds.includes(row.id)

             },

             click: this.$_handleSave

           }

         ]

       }

     ]),

     data: [

       {

         // 行编辑必须指定rowKey字段,默认是id,如果修改为其他字段,需要给表格指定row-key="字段名"

         id: '0',

         name: '子君',

         sex: '1',

         age: 18

       },

       {

         // 行编辑必须指定rowKey字段,默认是id,如果修改为其他字段,需要给表格指定row-key="字段名"

         id: '1',

         name: '子君1',

         sex: '0',

         age: 18

       }

     ],

     editIds: []

   }

 },

 methods: {

   $_handleEdit(row) {

     // 通过调用 startEditRow 可以开启行编辑

     this.$refs.table.startEditRow(row.id)

     // 记录开启了行编辑的id

     this.editIds.push(row.id)

   },

   $_handleSave(row) {

     // 点击保存的时候,通过endEditRow 结束行编辑

     this.$refs.table.endEditRow(row.id, (valid, result, oldRow) => {

       // 如果有表单验证,则valid会返回是否验证成功

       if (valid) {

         console.log('修改之后的数据', result)

         console.log('原始数据', oldRow)

         const index = this.editIds.findIndex(item => item === row.id)

         this.editIds.splice(index, 1)

       } else {

         // 如果校验失败,则返回校验的第一个输入框的异常信息

         console.log(result)

         this.$message.error(result.message)

       }

     })

   }

 }

}

</script>

不需要使用插槽就可以完成行编辑,是不是很开心。上述完整代码见 https://github.com/snowzijun/vue-element-table/blob/master/example/views/demo/row-edit.vue


效果如下图所示:




其他功能

除了上面的功能之外,表格还可以配置其他许多功能,比如


可以指定字段为链接列,需要给列配置link属性

可以通过插槽自定义顶部按钮,行操作按钮,行字段等

可以在按钮区域右侧通过插槽配置其他内容

其他等等

表格开发说明

通过上面的代码示例,我们已经知道了封装之后的表格可以完成哪些事情,接下来一起来看看表格是如何实现的。完整代码见 https://github.com/snowzijun/vue-element-table/tree/master/src/components/zj-table


表格布局

整个表格是通过JSX来封装的,因为JSX使用起来更加灵活。对于我们封装的表格,我们从竖向可以分为三部分,分别是顶部按钮区,中间表格区,底部分页区,如何去实现三个区域的布局呢,核心代码如下


render(h) {

   // 按钮区域

   const toolbar = this.$_renderToolbar(h)

   // 表格区域

   const table = this.$_renderTable(h)

   // 分页区域

   const page = this.$_renderPage(h)


   return (

     <div class="zj-table" style={{ height: this.tableContainerHeight }}>

       {toolbar}

       {table}

       {page}

     </div>

   )

 }

通过三个render函数分别渲染对应区域,然后将三个区域组合在一起。


渲染表格列

通过前文的讲解,我们可以将表格的列分为以下几种


常规列

行编辑列

操作按钮列

插槽列

链接列(文档后续完善)

嵌套列(文档后续完善)

   $_renderColumns(h, columns) {

     // 整体是否排序

     let sortable = this.sortable ? 'custom' : false

     return columns

       .filter(column => {

         const { hidden } = column

         if (hidden !== undefined) {

           if (typeof hidden === 'function') {

             return hidden({

               columns,

               column

             })

           }

           return hidden

         }

         return true

       })

       .map(column => {

         const {

           useSlot = false,

           // 如果存在操作按钮,则actions为非空数组

           actions = [],

           // 是否可编辑列, 对于可编辑列需要动态启用编辑

           editable = false,

           // 是否有嵌套列

           nests,

           // 是否可点击

           link = false

         } = column

         let newSortable = sortable

         if (column.sortable !== undefined) {

           newSortable = column.sortable ? 'custom' : false

         }

         column = {

           ...column,

           sortable: newSortable

         }

         if (nests && nests.length) {

           // 使用嵌套列

           return this.$_renderNestColumn(h, column)

         } else if (editable) {

           // 使用编辑列

           return this.$_renderEditColumn(h, column)

         } else if (useSlot) {

           // 使用插槽列

           return this.$_renderSlotColumn(h, column)

         } else if (actions && actions.length > 0) {

           // 使用操作列

           column.sortable = false

           return this.$_renderActionColumn(h, column)

         } else if (link) {

           // 使用链接列

           return this.$_renderLinkColumn(h, column)

         } else {

           // 使用默认列

           return this.$_renderDefaultColumn(h, column)

         }

       })

   },

行编辑列

当前表格行编辑支持input,select,datepicker,TimeSelect,InputNumber等组件,具体渲染代码如下所示


// 编辑单元格

   $_renderEditCell(h, field) {

     const components = {

       input: Input,

       select: ZjSelect,

       date: DatePicker,

       time: TimeSelect,

       number: InputNumber

     }

     const componentType = field.componentType

     const component = components[componentType]

     if (component) {

       return this.$_renderField(h, field, component)

     } else if (componentType === 'custom') {

       // 如果自定义,可以通过component指定组件

       return this.$_renderField(h, field, field.component)

     }

     return this.$_renderField(h, field, Input)

   },

   $_renderField(h, field, Component) {

     // 编辑行的id字段

     const { rowId, events = {}, nativeEvents = {} } = field


     const getEvents = events => {

       const newEvents = {}

       Object.keys(events).forEach(key => {

         const event = events[key]

         newEvents[key] = (...rest) => {

           const args = [

             ...rest,

             {

               rowId,

               row: this.editRowsData[rowId],

               value: this.editRowsData[rowId][field.prop]

             }

           ]

           return event(...args)

         }

       })

       return newEvents

     }

     // 事件改写

     const newEvents = getEvents(events)

     const newNativeEvents = getEvents(nativeEvents)

     return (

       <Component

         size="small"

         on={newEvents}

         nativeOn={newNativeEvents}

         v-model={this.editRowsData[rowId][field.prop]}

         {...{

           attrs: field,

           props: field

         }}

       />

     )

   }

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务


让你的 commit 更有价值

seo达人

提交规范

AngularJS 在开发者文档中关于 git commit 的指导说明,提到严格的 git commit 格式规范可以在浏览项目历史的过程中看到更易读的信息,并且能用 git commit 的信息直接生成 AngularJS 的 change log 。


commit messages 格式规范

commit messages 由 header 、body 、footer 组成。


header 又包含 type 、scope 、subject 。header 是必需的,不过其中的 scope 是可选的。


body 和 footer 可以省略。


<type>(<scope>): <subject>

// 空行

<BLANK LINE>

<body>

// 空行

<BLANK LINE>

<footer>

注:为了能在 github 以及各种 git 工具中看得更清晰,commit messages 的每一行都不要超过 100 个字符。

Header

Type

类型必须是以下几种之一:


feat: 新功能

fix: bug 修复

docs: 仅修改文档

style: 修改格式(空格,格式化,省略分号等),对代码运行没有影响

refactor: 重构(既不是修 bug ,也不是加功能)

build: 构建流程、外部依赖变更,比如升级 npm 包、修改 webpack 配置等

perf: 性能优化

test: 测试相关

chore: 对构建过程或辅助工具和库(如文档生成)的更改

ci: ci 相关的更改

除此之外,还有一个特殊的类型 revert ,如果当前提交是为了撤销之前的某次提交,应该用 revert 开头,后面加上被撤销的提交的 header,在 body 中应该注明: This reverts commit <hash>. ,hash 指的就是将要被撤销的 commit SHA 。


// 例如


revert: feat(user): add user type


This reverts commit ca16a365467e17915f0273392f4a13331b17617d.

Scope

scope 可以指定提交更改的影响范围,这个视项目而定,当修改影响超过单个的 scope 时,可以指定为 * 。


Sbuject

subject 是指更改的简洁描述,长度约定在 50 个字符以内,通常遵循以下几个规范:


用动词开头,第一人称现在时表述,例如:change 代替 changed 或 changes

第一个字母小写

结尾不加句号(.)

Body

body 部分是对本地 commit 的详细描述,可以分成多行。


跟 subject 类似,用动词开头,第一人称现在时表述,例如:change 代替 changed 或 changes。


body 应该说明修改的原因和更改前后的行为对比。


Footer

footer 基本用在这两种情况:


不兼容的改动( Breaking Changes ),通常用 BREAKING CHANGE: 开头,后面跟一个空格或两个换行符。剩余的部分就是用来说明这个变动的信息和迁移方法等。

关闭 Issue, github 关闭 Issue 的例子

// BREAKING CHANGE: 的例子

BREAKING CHANGE: isolate scope bindings definition has changed and

   the inject option for the directive controller injection was removed.


   To migrate the code follow the example below:


   Before:


   scope: {

     myAttr: 'attribute',

     myBind: 'bind',

     myExpression: 'expression',

     myEval: 'evaluate',

     myAccessor: 'accessor'

   }


   After:


   scope: {

     myAttr: '@',

     myBind: '@',

     myExpression: '&',

     // myEval - usually not useful, but in cases where the expression is assignable, you can use '='

     myAccessor: '=' // in directive's template change myAccessor() to myAccessor

   }


   The removed `inject` wasn't generaly useful for directives so there should be no code using it.




// Closes Issue 例子

Closes #2314, #3421

完整的例子

例一: feat

feat($browser): onUrlChange event (popstate/hashchange/polling)


Added new event to $browser:

- forward popstate event if available

- forward hashchange event if popstate not available

- do polling when neither popstate nor hashchange available


Breaks $browser.onHashChange, which was removed (use onUrlChange instead)

例二: fix

fix($compile): couple of unit tests for IE9


Older IEs serialize html uppercased, but IE9 does not...

Would be better to expect case insensitive, unfortunately jasmine does

not allow to user regexps for throw expectations.


Closes #392

Breaks foo.bar api, foo.baz should be used instead

例三: style

style($location): add couple of missing semi colons

查看更多例子

规范 commit message 的好处

首行就是简洁实用的关键信息,方便在 git history 中快速浏览

具有详实的 body 和 footer ,可以清晰的看出某次提交的目的和影响

可以通过 type 过滤出想要查找的信息,也可以通过关键字快速查找相关提交

可以直接从 commit 生成 change log

// 列举几个常用的 log 参数


// 输出 log 的首行

git log --pretty=oneline


// 只输出首行的 commit 信息。不包含 hash 和 合并信息等

git log --pretty=format:%s


// 查找有关“更新菜单配置项”的提交

git log --grep="更新菜单配置项"


// 打印出 chenfangxu 的提交

git log --author=chenfangxu


// 红色的短 hash,黄色的 ref , 绿色的相对时间

git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset'

用工具实现规范提交

上面介绍了规范提交的格式,如果让各位同学在 git commit 的时候严格按照上面的规范来写,首先心智是有负担的,得记住不同的类型到底是用来定义什么的,subject 怎么写,body 怎么写,footer 要不要写。其次,对人的规范大部分都是反人性的,所以很可能在过不了多久,就会有同学渐渐的不按照规范来写。靠意志力来控制自己严格按照规范来写是需要额外耗费一些精力的,把精力耗费在这种事情上面实在有些浪费。


用工具实现规范提交的方案,一种是在提交的时候就提示必填字段,另一种是在提交后校验字段是否符合规范。这两种在实际项目中都是很有必要的。


Commitizen

Zen-like commit messages for internet citizens. 嗯~~一种禅意

Commitizen 是一个帮助撰写规范 commit message 的工具。他有一个命令行工具 cz-cli,接下来会把使用 Commitizen 分成几个阶段来介绍。


体验 git cz

// 全局安装 Commitizen

npm install -g commitizen

你的仓库可能还不是对 Commitizen 友好的,此时运行 git cz 的效果跟 git commit 一样,也就是没有效果。 不过,可以执行 npx git-cz 来体验。


如果想直接运行 git cz 实现语义化的提交,可以根据 streamich/git-cz 文档中说的全局安装 git cz。


// 全局安装 git cz

npm install -g git-cz

除此之外还有一种更推荐的方式,就是让你的仓库对 Commitizen 友好。


Commitizen 友好

全局安装 Commitizen 后,用 cz-conventional-changelog 适配器来初始化你的项目


// 初始化 cz-conventional-changelog 适配器

commitizen init cz-conventional-changelog --save-dev --save-exact

上面的初始化做了三件事:


安装 cz-conventional-changelog 依赖

把依赖保存到 package.json 的 dependencies 或 devDependencies 中

在根目录的 package.json 中 添加如下所示的 config.commitizen

"config": {

   "commitizen": {

     "path": "./node_modules/cz-conventional-changelog"

   }

 }

或者,在项目根目录下新建一个 .czrc 文件,内容设置为


{

 "path": "cz-conventional-changelog"

}

现在运行 git cz 效果如下:




cz-customizable 自定义中文配置

通过上面的截图可以看到,提交的配置选项都是英文的,如果想改成中文的,可以使用 cz-customizable 适配器。


运行下面的命令,注意之前已经初始化过一次了,这次再初始化,需要加 --force 覆盖


npm install cz-customizable --save-dev


commitizen init cz-customizable --save-dev --save-exact --force

现在 package.json 中 config.commitizen 字段为:


"config": {

   "commitizen": {

     "path": "./node_modules/cz-customizable"

   }

 }

cz-customizable 文档中说明了查找配置文件的方式有三种,我们按照第一种,在项目根目录创建一个 .cz-config.js 的文件。按照给出的示例 cz-config-EXAMPLE.js 编写我们的 config。 commit-type 可以参考 conventional-commit-types 。


可以点击查看我配置好的文件 qiqihaobenben/commitizen-git/.cz-config.js ,里面中详细的注释。


commitlint 校验提交

Commitizen 文档中开始就介绍到,Commitizen 可以在触发 git commit 钩子之前就能给出提示,但是也明确表示提交时对 commit messages 的校验也是很有用的。毕竟即使用了 Commitzen,也是能绕过去,所以提交最后的校验很重要。


commitlint 可以检查 commit messages 是否符合常规提交格式,需要一份校验配置,推荐 @commitlint/config-conventional 。


npm i --save-dev @commitlint/config-conventional @commitlint/cli

在项目根目录创建 commitlint.config.js 文件并设置校验规则:


module.exports = {

 extends: ["@commitlint/config-conventional"],

 // rules 里面可以设置一些自定义的校验规则

 rules: {},

};

在项目中安装 husky ,并在项目根目录新建 husky.config.js 文件,加入以下设置:


// 安装 husky

npm install --save-dev husky



// husky.config.js 中加入以下代码

module.exports = {

 "hooks": {

   "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"

 }

}

注意:因为 @commitlint/config-conventional 校验规则遵循 Angular 的规范, 所以我们在用 cz-customizable 自定义中文配置时, 是按照给出的符合 Angular 规范的示例 cz-config-EXAMPLE.js 编写.cz-config.js 的。但是如果你自定义的 Commitizen 配置不符合 Angular 规范,可以使用 commitlint-config-cz 设置校验规则。(推荐还是按照 Angular 规范进行 cz-customizable 自定义配置)

// 安装 commitlint-config-cz

npm install commitlint-config-cz --save-dev



// commitlint.config.js 改为

module.exports = {

 extends: [

   'cz'

 ]

};

git commit 触发 git cz

在提交的时候,我们都习惯了 git commit ,虽然换成 git cz 不难,但是如果让开发者在 git commit 时无感知的触发 git cz 肯定是更好的,

而且也能避免不熟悉项目的人直接 git commit 提交一些不符合规范的信息。


我们可以在 husky.config.js 中设置:


"hooks": {

 "prepare-commit-msg": "exec < /dev/tty && git cz --hook || true",

}

注意: 在 window 系统,可能需要在 git base 中才能生效。

生成 CHANGELOG

standard-version

是一个使用 semver 和 conventional-commits 支持生成 CHANGELOG 进行版本控制的实用程序。

standard-version 不只是能生成 CHANGELOG , 还能根据 commit 的 type 来进行版本控制。


// 安装 standard-verison

npm i --save-dev standard-version


// 在 package.json 中的 scripts 加入 standard-version

{

 "scripts": {

   "release": "standard-version"

 }

}

示例项目

可以查看 commitizen-git ,里面归纳了快速配置 Commitizen 友好仓库的步骤。

差不多三五分钟就能搞定。


可以看一下配置完后,执行 git commit 的效果。




扩展

更复杂的自定义提示

cz-customizable 中自定义配置项通常情况是够用的,

commitlint 中校验的规则基本上也是够用的,但是会有比较硬核的开发者会觉得还是不够,还要更多。比如一些 prompt 更加自定义,

提交时询问的 question 添加更多的逻辑,比如可以把一些重要的字段校验提前到 Commitizen 中,或者添加更多自定义的校验。


如果真想这么干,那就去 fork 一份 cz-conventional-changelog 或者 cz-customizable 来改,

或者直接自己写一个 adapter。


Commitizen 友好徽章

如果把仓库配置成了对 Commitizen 友好的话,可以在 README.md 中加上这个小徽章

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

一文读懂 Web 安全

seo达人

Web 安全是互联网中不可或缺的一个领域,这个领域中诞生了大量的黑帽子与白帽子,他们都是安全领域的王者,在平时里,他们利用各种巧妙的技术互相博弈,时不时就会掀起一场 Web 安全浪潮,真可谓神仙打架,各显神通。


本文从一个吃瓜群众的角度,聊一聊 Web 安全的一些有趣故事。


安全世界观

安全攻防案例

总结与思考

安全世界观

在互联网发展之初,IE 浏览器垄断的时期,大家上网的目的都很单纯,主要通过浏览器分享信息,获取新闻。但随着互联网的不断发展发展,一个网页能做的事情越来越多,除了看新闻,我们还可以看视频、玩游戏、购物、聊天等,这些功能都大大丰富了我们的生活。


随着网页功能的逐渐增多,就开始出现了一些黑帽子,他们试图通过一些技术手段来牟取利益。在我小的时候,印象最深的就是木马病毒,它可以监控你的键盘,将你在键盘上敲打的内容发送到黑客的机器上,黑客通过分析这些内容,很容易就能得到你的游戏账号和密码。


在这之后,就诞生出了一些杀毒软件,致力于解决网络上的各种病毒,随着不断地发展,杀毒软件已经成为一台电脑必不可少的软件。


为什么会出现这样的安全问题?

安全归根到底是信任的问题,如果所有人都按照正常的流程去上网,不去谋取私利,也就没有安全问题可谈了。


安全的根本在于信任,但要让所有人互相信任谈何容易。在当前阶段,我们可以做到:持续做好安全防护,让漏洞越来越少,非法攻击越来越困难,这样就能逐渐减少黑帽子的数量,让病毒制造者越来越少。


如何做好安全

要做好安全,首先得理解安全问题的属性,前人通过无数实践,最后将安全的属性总结为安全三要素,分别为:机密性、完整性、可用性。


机密性


保护数据内容不被泄露。

通常使用加密的方法。

完整性


保护数据内容是完整的、没有被篡改。

通常使用数字签名的方法。

可用性


数据随时都能够使用。

通常是在防御 DOS。

有了安全 3 要素之后,我们就可以对安全问题进行评估了。


资产等级划分


找出最重要的数据。

找出最重要数据的宿主空间,如:在数据库里,那么数据库就得重点防御。

找出数据库的宿主空间,如:在一台服务器上,那么这台服务器就得做次等防御。

找出服务器的宿主空间,如:在 OSI 网络层级上,那么在网络层面就得做一般防御。

威胁分析


找出威胁(可能造成危害的来源)。

找出风险(可能出现的损失叫做风险)。

风险分析


采取多标准决策分析,即:风险 = 威胁等级 * 威胁可行性。

计算所有的威胁,将最终的风险进行排序,优先解决风险大的问题。

确认解决方案


找出不安全的实现方式,并确定解决方案。

解决方案不要改变商业需求的初衷。

解决方案需对用户透明,不要改变用户的习惯。

做好安全评估之后,我们就有了一份安全解决方案,后续的安全工作只需按照这个方案去做,就没有任何问题。


安全的原则

有了安全解决方案之后,我们还可以制定一些安全原则,遵守原则做事,可以让我们事半功倍。


黑名单、白名单原则


白名单方案指的是给安全的资源授权。

黑名单方案指的是禁用不安全的资源。

我们应该优先使用白名单方案,因为黑名单通常统计不完所有的不安全资源。

如:XSS 攻击的方式非常多,可以通过 script、css、image 标签等,尽管你将这些标签都加入黑名单,也不能保证其他的标签都没有 XSS 的攻击隐患。

最小权限原则


只授予必要的权限,不要过度授权,减少出错机会。

如:普通权限的 Linux 用户只能操作 ~ 文件夹下的目录,如果有人想删库跑路,在执行 rm -rf / 时,就会提示无权限。

纵深防御原则


这条原则类似 木桶理论,安全水平往往取决于最短的那块板。

即:不要留下短板,黑帽子们往往可以利用短板为突破口,挖掘更大的漏洞。

数据与代码分离原则


当用户数据被当成代码执行时,混淆了数据和代码的边界,从而导致安全问题。

如:XSS 就是利用这一点去攻击的。

不可预测性原则


这条原则是为了提高攻击门槛,有效防止基于篡改、伪造的攻击。

如:数据库中使用 uuid 代替 number 型的自增主键,可以避免 id 被攻击者猜到,从而进行批量操作。

token 也是利用不可预测性,攻击者无法构造 token 也就无法进行攻击。

有了这些安全原则,我们就可以开干了,接下来介绍几个常见的攻防案例。


安全攻防案例

安全攻防的案例非常多,这里主要介绍几个出镜率比较高的安全问题。


客户端攻击

XSS 攻击

CSRF 攻击

点击劫持

XSS 攻击

XSS 攻击的本质是将用户数据当成了 HTML 代码一部分来执行,从而混淆原本的语义,产生新的语义。




如图所示,我们注册了一个 <script>alert(document.cookie)</script> 的用户名,所有能看到此用户名字的页面,都会弹出当前浏览器的 Cookie,如果代码的逻辑是将 Cookie 发送到攻击者的网站,攻击者就能冒充当前用户进行登录了。


XSS 攻击方式有很多,所有和用户交互的地方,都有可能存在 XSS 攻击。


例如:


所有 input 框。

window.location。

window.name。

document.referrer。

document.cookie。

localstorage。

...

由于页面中与用户交互的地方非常多,肯定还有一些 XSS 的攻击方式没有被发现,而一旦被黑帽子发现,就可能造成严重的影响,所以我们务必引起重视。


XSS 攻击影响

被 XSS 攻击成功后,攻击者就可以获取大量的用户信息,例如:


识别用户 UA。

识别用户浏览器扩展。

识别用户浏览过的网站。


通过 CSS 的 Visited 属性。

获取用户真实的 IP。


通过 WebRTC 等。

盗取 Cookie


伪造用户登录,窃取用户资料。

XSS 钓鱼。


向页面注入一个登录弹窗,让用户认为是网站内的登录弹窗(其实是钓鱼网站的),一旦用户登录,账号密码就泄露给了钓鱼网站。

XSS 攻击防御

目前来说,XSS 已经得到了互联网行业的重视,许多开发框架都内置了安全的 HTML 渲染方法。


我们也可以自定义进行一些安全配置。


配置 HTTP 中的 http-only 头,让前端 JS 不能操作 Cookie。

输入检查,在用户提交数据时,使用 XssFilter 过滤掉不安全的数据。

输出检查,在页面渲染的时候,过滤掉危险的数据。

CSRF 攻击

CSRF(Cross-site request forgery)跨站请求伪造,是一种利用用户身份,执行一些用户非本意的操作。




如图所示:


用户先登录了服务器 B,然后去访问服务器 C。

服务器 C 通过恶意脚本,冒充 A 去调用服务器 B 上的某个功能,

对于服务器 B 来说,还以为这是 A 发起的请求,就当作正常请求处理了。

试想一下,如果 C 冒充 A 进行了一次转账,必定会造成大量的经济损失。


CSRF 防御方式

防御 CSRF 主要有以下几种方式:


验证码


每一次请求都要求用户验证,以确保请求真实可靠。

即:利用恶意脚本不能识别复杂的验证码的特点,保证每次请求都是合法的。

Referer 检查


检查发起请求的服务器,是否为目标服务器。

即:HTTP 请求中的 Referer 头传递了当前请求的域名,如果此域名是非法服务器的域名,则需要禁止访问。

Token


利用不可预测性原则,每一请求必须带上一段随机码,这段随机码由正常用户保存,黑帽子不知道随机码,也就无法冒充用户进行请求了。

点击劫持

点击劫持是一种视觉欺骗的攻击手段。攻击者将需要攻击的网站通过 iframe 嵌套的方式嵌入自己的网页中,并将 iframe 设置为透明,在页面中透出一个按钮诱导用户点击。


就像一张图片上面铺了一层透明的纸一样,你看到的是攻击者的页面,但是其实这个页面只是在底部,而你真正点击的是被攻击者透明化的另一个网页。




如果所示,当你点击了页面上的按钮之后,本以为会...... ,而真正执行的操作是关注了某人的博客。


点击劫持防御

由于点击劫持主要通过 iframe,所以在防御时,主要基于 iframe 去做。


方案一:frame busting


正常网站使用 JS 脚本判断是否被恶意网站嵌入,如:博客网站监测到被一个 iframe 打开,自动跳转到正常的页面即可。

if (self !== top) {  // 跳回原页面  top.location = self.location;}

方案二:使用 HTTP 中的 x-frame-options 头,控制 iframe 的加载,它有 3 个值可选:


DENY,表示页面不允许通过 iframe 的方式展示。

SAMEORIGIN,表示页面可以在相同域名下通过 iframe 的方式展示。

ALLOW-FROM,表示页面可以在指定来源的 iframe 中展示。

配置 iframe 的 sandbox 属性


sandbox = "allow-same-origin" 则只能加载与主站同域的资源。

服务器端攻击

服务器端的攻击的方式也非常多,这里列举几个常见的。


SQL 注入攻击

文件上传漏洞

登录认证攻击

应用层拒绝服务攻击

webServer 配置安全

SQL 注入攻击

SQL 注入和 XSS 一样,都是违背了数据和代码分离原则导致的攻击方式。


如图所示,我们利用 SQL 注入,就能在不需要密码的情况下,直接登录管理员的账号。




攻击的前提是:后端只用了简单的拼接 SQL 的方式去查询数据。


# 拼接出来的 sql 如下:select * from user where username = 'admin' or 1=1 and password = 'xxx'# 无论密码输入什么,这条 sql 语句都能查询到管理员的信息

除此之外,SQL 注入还有以下几种方式:


使用 SQL 探测,猜数据库表名,列名。


通过 MySQL 内置的 benchmark 探测数据库字段。

如:一段伪代码 select database as current if current[0]==='a',benchmark(10000,'猜对了') 如果表明猜对了,就延迟 10 s 并返回成功。

使用存储过程执行系统命令


通过内置的方法或存储过程执行 shell 脚本。

如:xp_cmdshell、sys_eval、sys_exec 等。

字符串截断


如:MySQL 在处理超长的字符串时,会显示警告,但会执行成功。

注册一个 admin + 50 个空格的用户,会触发截断,最终新增一个 admin 用户,这样就能拥有管理员权限了。

SQL 注入防御

防止 SQL 注入的最好的办法就是,不要手动拼接 SQL 语句。


最佳方案,使用预编译语句绑定变量


通常是指框架提供的拼接 SQL 变量的方法。

这样的语义不会发生改变,变量始终被当成变量。

严格限制数据类型,如果注入了其他类型的数据,直接报错,不允许执行。

使用安全的存储过程和系统函数。

CRLF 注入

在注入攻击中,换行符注入也是非常常见的一种攻击方式。


如果在 HTTP 请求头中注入 2 个换行符,会导致换行符后面的所有内容都被解析成请求实体部分。

攻击者通常在 Set-Cookie 时,注入换行符,控制请求传递的内容。

文件上传漏洞

上传文件是网页开发中的一个常见功能,如果不加处理,很容易就会造成攻击。




如图所示,攻击者上传了一个木马文件,并且通过返回的 URL 进行访问,就能控制服务器。


通常我们会控制上传文件的后缀名,但也不能完全解决问题,攻击者还可以通过以下方式进行攻击:


伪造正常文件


将木马文件伪装成正常的后缀名进行上传。

如果要避免这个问题,我们可以继续判断上传文件的文件头前 10 个字节。

Apache 解析方式是从后往前解析,直到找到一个认识的后缀名为止


如:上传一个 abc.php.rar.rar.rar 能绕过后缀名检查,但在执行时,被当成一个 php 文件进行执行。

IIS 会截断分号进行解析


如:abc.asp;xx.png 能绕过后缀名检查,但在执行时,被当成一个 asp 文件进行执行。

HTTP PUT 方法允许将文件上传到指定位置


通过 HTTP MOVE 方法,还能修改上传的文件名。

通过二者配合,就能先上传一个正常的后缀名,然后改为一个恶意的后缀名。

PHP CGI 路径问题


执行 http://abc.com/test.png/xxx.php 时,会把 test.png 当做 php 文件去解析。

如果用户正好是把一段恶意的 php 脚本当做一张图片进行上传,就会触发这个攻击。

文件上传漏洞防御

防御文件上传漏洞,可以从以下几点考虑:


将文件上传的目录设置为不可执行。

判断文件类型


检查 MIME Type,配置白名单。

检查后缀名,配置白名单。

使用随机数改写文件名和文件路径


上传文件后,随机修改文件名,让攻击者无法执行攻击。

单独设置文件服务器的域名


单独做一个文件服务器,并使用单独的域名,利用同源策略,规避客户端攻击。

通常做法是将静态资源存放在 CDN 上。

登录认证攻击

登录认证攻击可以理解为一种破解登录的方法。攻击者通常采用以下几种方式进行破解:


彩虹表


攻击者通过搜集大量明文和 MD5 的对应关系,用于破解 MD5 密文找出原文。

对于彩虹表中的 MD5 密码,我们可以加盐,进行二次加密,避免被破解。

Session Fixation 攻击


利用应用系统在服务器的 SessionID 固定不变机制,借助他人用相同的 SessionID 获取认证和授权。

攻击者登录失败后,后端返回了 SessionID,攻击者将 SessionID 交给正常用户去登录,登录成功后,攻击者就能使用这个 SessionID 冒充正常用户登录了。

如果浏览器每一次登录都刷新 SessionID 可以避免这个问题。

Session 保持攻击


有些时候,后端出于用户体验考虑,只要这个用户还活着,就不会让这个用户的 Session 失效。

攻击者可以通过不停发起请求,可以让这个 Session 一直活下去。

登录认证防御方式

多因素认证


密码作为第一道防御,但在密码验证成功后,我们还可以继续验证:动态口令,数字证书,短信验证码等,以保证用户安全。

由于短信和网页完全是 2 套独立的系统,攻击者很难获取到短信验证码,也就无法进行攻击。

除此之外,前端登录认证还有多种方式,如果你对此感兴趣,可以参考我之前写的 前端登录,这一篇就够了。


应用层拒绝服务攻击

应用层拒绝服务攻击,又叫 DDOS 攻击,它指的是利用大量的请求造成资源过载,导致服务器不可用。




通常有以下几种 DDOS 攻击方式:


SYN Flood 洪水攻击


利用 HTTP 3 次握手机制,消耗服务器连接资源。

如:攻击者发起大量的 HTTP 请求,但并不完成 3 次握手,而是只握手 2 次,这时服务器端会继续等待直至超时。这时的服务器会一直忙于处理大量的垃圾请求,而无暇顾及正常请求。

Slowloris 攻击


以非常低的速度发送 HTTP 请求头,消耗服务器连接资源。

如:攻击者发送大量 HTTP 请求,但每个请求头都发的很慢,每隔 10s 发送一个字符,服务器为了等待数据,不得始终保持连接,这样一来,服务器连接数很快就被占光了。

HTTP POST DOS


发送 HTTP 时,指定一个非常大的 Content-Length 然后以很长的间隔发送,消耗服务器连接资源。

CC 攻击


针对一些非常消耗资源的页面,不断发起请求。

如:页面中的某些页面,需要后端做大量的运算,或者需要做非常耗时的数据库查询。在大量的请求下,服务器的 CPU、内存等资源可能就被占光了。

Server Limit DOS


通过 XSS 注入一段超长的 Cookie,导致超出 Web 服务器所能承受的 Request Header 长度,服务器端就会拒绝此服务。

ReDOS


针对一些缺陷的正则表达式,发起大量请求,耗光系统资源。

应用层拒绝服务攻击防御

对于应用层拒绝服务攻击,目前也没有特别完美的解决方案,不过我们还是可以进行一些优化。


应用代码做好性能优化


合理使用 Redis、Memcache 等缓存方案,减少 CPU 资源使用率。

网络架构上做好优化


后端搭建负载均衡。

静态资源使用 CDN 进行管理。

限制请求频率


服务器计算所有 IP 地址的请求频率,筛选出异常的 IP 进行禁用。

可以使用 LRU 算法,缓存前 1000 条请求的 IP,如果有 IP 请求频率过高,就进行禁用。

其实,处理 DDOS 核心思路就是禁用不可信任的用户,确保资源都是被正常的用户所使用。


WebServer 配置安全

我们在部署 web 应用的时候,经常会用到 Nginx、Apache、IIS、Tomcat、Jboss 等 Web 服务器,这些服务器本身也存在一些安全隐患,如果配置不当,很容易收到攻击。


在配置 Web 服务器时,可以参考以下几点:


以用户权限运行 Web 服务器


遵守最小权限原则,以最小权限身份运行 Web 服务器,限制被入侵后的权限。

删除可视化后台


运行 Tomcat、Jboss 等 Web 服务器时,默认会开启一个可视化的运营后台,运行在 8080 端口,并且第一次访问是没有认证的。

攻击者可以利用可视化后台,远程加载一段 war 包或者上传木马文件,进行控制。

及时更新版本


主流的 Web 服务器,每隔一段时间就会修复一些漏洞,所以记得及时更新版本。

总结与思考

本文介绍了 Web 安全的基本概念,以及大量的攻防技巧,其实这只是 Web 安全中的冰山一角,如果你对此感兴趣,不妨在安全领域继续深耕学习,一定能看到更广阔一片天。


对于一个开发者来说,我们应该在写代码时就将安全考虑其中,形成自己的一套安全开发体系,做到心中有安全,时时考虑安全,就能无形之中化解不法分子的攻击。

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

巧用伪元素before和after制作绚丽效果

seo达人

CSS :before 选择器

定义和说明

:before 选择器向选定的元素前插入内容。

使用content 属性来指定要插入的内容。


CSS :after 选择器

定义和说明

:after 选择器向选定的元素之后插入内容。

使用content 属性来指定要插入的内容。


这两个伪元素会在真正页面元素之前和之后插入一个额外的元素,从技术角度上讲,它们与下面的HTML标记是等效的。


1.伪类光圈



<div class="hover-circle">CSS</div>

.hover-circle {

 width: 100%;

 display: flex;

 align-items: center;

 justify-content: center;

 height: 100%;

 font-size: 3rem;

 letter-spacing: 0.3rem;

 font-weight: bold;

 position: relative;

 cursor: pointer;

 color: #666;

}


.hover-circle::before {

 width: 8.5rem;

 height: 8.5rem;

 border: 3px solid pink;

 content: "";

 border-radius: 50%;

 position: absolute;

 opacity: 0;

}


.hover-circle::after {

 width: 7.2rem;

 height: 7.2rem;

 border: 6px solid pink;

 content: "";

 border-radius: 50%;

 position: absolute;

 opacity: 0;

}


.hover-circle:hover::before,

.hover-circle:hover::after {

 animation-duration: 0.8s;

 animation-delay: 0.2s;

 animation: circle 0.8s;

}


@keyframes circle {

 0% {

   opacity: 0;

   scale: 1;

 }


 25% {

   opacity: 0.25;

 }


 50% {

   opacity: 0.5;

   scale: 1.03;

 }


 75% {

   opacity: 0.75;

 }


 100% {

   opacity: 1;

   scale: 1.03;

 }

}

2.伪类括号效果



<div class="hover-text">CSS</div>

.hover-text {

 width: 100%;

 display: flex;

 align-items: center;

 justify-content: center;

 height: 100%;

 font-size: 3rem;

 letter-spacing: 0.3rem;

 font-weight: bold;

 position: relative;

 cursor: pointer;

 color: #666;

}


.hover-text::before {

 content: "[";

 position: absolute;

 left: 0.8rem;

 opacity: 0;

 color: #999;

}


.hover-text::after {

 content: "]";

 position: absolute;

 right: 0.8rem;

 opacity: 0;

 color: #999;

}


.hover-text:hover::before {

 animation-duration: 0.8s;

 animation-delay: 0.2s;

 animation: hovertext1 0.8s;

}


.hover-text:hover::after {

 animation-duration: 0.8s;

 animation-delay: 0.2s;

 animation: hovertext2 0.8s;

}


@keyframes hovertext1 {

 0% {

   opacity: 0;

   left: 0.8rem;

 }


 100% {

   opacity: 1;

   left: 0.5rem;

 }

}


@keyframes hovertext2 {

 0% {

   opacity: 0;

   right: 0.8rem;

 }


 100% {

   opacity: 1;

   right: 0.5rem;

 }

}

3.炫酷丝带效果

双边丝带



<div class="tc">

   <div class="title1"><span>距离结束还有10天</span></div>

</div>

.title1 {

 position: relative;

 display: inline-block;

}


.title1 span {

 position: relative;

 z-index: 2;

 display: inline-block;

 padding: 0 15px;

 height: 32px;

 line-height: 32px;

 background-color: #dc5947;

 color: #fff;

 font-size: 16px;

 box-shadow: 0 10px 6px -9px rgba(0, 0, 0, 0.6);

}


.title1 span::before,

.title1 span::after {

 position: absolute;

 bottom: -6px;

 border-width: 3px 5px;

 border-style: solid;

 content: "";

}


.title1 span::before {

 left: 0;

 border-color: #972f22 #972f22 transparent transparent;

}


.title1 span::after {

 right: 0;

 border-color: #972f22 transparent transparent #972f22;

}


.title1::before,

.title1::after {

 position: absolute;

 top: 6px;

 content: "";

 border-style: solid;

 border-color: #dc5947;

}


.title1::before {

 left: -32px;

 border-width: 16px 26px 16px 16px;

 border-left-color: transparent;

}


.title1::after {

 right: -32px;

 border-width: 16px 16px 16px 26px;

 border-right-color: transparent;

}

右边丝带



<span class="title2">距离结束还有10天</span>

.title2 {

 position: relative;

 display: inline-block;

 padding: 0 15px;

 height: 32px;

 line-height: 32px;

 background-color: #dc5947;

 color: #fff;

 font-size: 16px;

}


.title2::before {

 position: absolute;

 top: -4px;

 left: 0;

 border-width: 2px 4px;

 border-style: solid;

 border-color: transparent #972f22 #972f22 transparent;

 content: "";

}


.title2::after {

 position: absolute;

 top: 0;

 right: -8px;

 border-width: 16px 8px 16px 0;

 border-style: solid;

 border-color: #dc5947 transparent #dc5947 #dc5947;

 content: "";

}

箭头丝带



<span class="title3">距离结束还有10天</span>

.title3 {

 position: relative;

 display: inline-block;

 margin-right: 16px;

 padding: 0 10px;

 height: 32px;

 line-height: 32px;

 background-color: #dc5947;

 color: #fff;

 font-size: 16px;

}


.title3::before {

 position: absolute;

 top: 0;

 left: -16px;

 border-width: 16px 16px 16px 0;

 border-style: solid;

 border-color: transparent #dc5947 transparent transparent;

 content: "";

}


.title3::after {

 position: absolute;

 top: 0;

 right: -16px;

 border-width: 16px 16px 16px 0;

 border-style: solid;

 border-color: #dc5947 transparent #dc5947 #dc5947;

 content: "";

}

多个箭头丝带



<div class="mt30 pl16">

   <span class="title3">距离结束还有10天</span>

   <span class="title3 ml5">距离结束还有10天</span>

   <span class="title3 ml5">距离结束还有10天</span>

</div>

.title4 {

 width: 200px;

 height: 140px;

 position: absolute;

 top: -8px;

 left: -8px;

 overflow: hidden;

}


.title4::before {

 position: absolute;

 left: 124px;

 border-radius: 8px 8px 0 0;

 width: 16px;

 height: 8px;

 background-color: #972f22;

 content: "";

}


.title4::after {

 position: absolute;

 left: 0;

 top: 124px;

 border-radius: 0 8px 8px 0;

 width: 8px;

 height: 16px;

 background-color: #972f22;

 content: "";

}


.title4 span {

 display: inline-block;

 text-align: center;

 width: 200px;

 height: 40px;

 line-height: 40px;

 position: absolute;

 top: 30px;

 left: -50px;

 z-index: 2;

 overflow: hidden;

 -ms-transform: rotate(-45deg);

 -moz-transform: rotate(-45deg);

 -webkit-transform: rotate(-45deg);

 -o-transform: rotate(-45deg);

 transform: rotate(-45deg);

 border: 1px dashed #fff;

 box-shadow: 0 0 0 3px #dc5947, 0 14px 7px -9px rgba(0, 0, 0, 0.6);

 background-color: #dc5947;

 color: #fff;

}

悬挂标签



<div class="pr mt30" style="background-color: #eee; height: 200px;">

   <div class="title4"><span>企业热门动态</span></div>

   <div class="title5"><span>企业热门动态</span></div>

</div>

.title5 {

 width: 140px;

 height: 200px;

 position: absolute;

 top: -8px;

 right: -8px;

 overflow: hidden;

}


.title5::before {

 position: absolute;

 right: 124px;

 border-radius: 8px 8px 0 0;

 width: 16px;

 height: 8px;

 background-color: #972f22;

 content: "";

}


.title5::after {

 position: absolute;

 right: 0;

 top: 124px;

 border-radius: 0 8px 8px 0;

 width: 8px;

 height: 16px;

 background-color: #972f22;

 content: "";

}


.title5 span {

 display: inline-block;

 text-align: center;

 width: 200px;

 height: 40px;

 line-height: 40px;

 position: absolute;

 top: 30px;

 right: -50px;

 z-index: 2;

 overflow: hidden;

 -ms-transform: rotate(45deg);

 -moz-transform: rotate(45deg);

 -webkit-transform: rotate(45deg);

 -o-transform: rotate(45deg);

 transform: rotate(45deg);

 border: 1px dashed #fff;

 box-shadow: 0 0 0 3px #dc5947, 0 14px 7px -9px rgba(0, 0, 0, 0.6);

 background-color: #dc5947;

 color: #fff;

}

4.几何图形

三角形



<div class="triangle"></div>

.triangle {

 width: 0;

 height: 0;

 margin: 50px auto;

 border-bottom: 100px solid #dc5947;

 border-left: 50px solid transparent;

 border-right: 50px solid transparent;

 cursor: pointer;

 transform: scale(1.2);

 transition: 0.5s;

}

五角星



<div class="pentagram"></div>

.pentagram {

 width: 0;

 height: 0;

 margin: 100px auto;

 position: relative;

 border-bottom: 70px solid #dc5947;

 border-left: 100px solid transparent;

 border-right: 100px solid transparent;

 -webkit-transform: rotate(35deg);

 -moz-transform: rotate(35deg);

 -ms-transform: rotate(35deg);

 -o-transform: rotate(35deg);

 transform: rotate(35deg);

 -webkit-transform: scale(1), rotate(35deg);

 -moz-transform: scale(1), rotate(35deg);

 -ms-transform: scale(1), rotate(35deg);

 -o-transform: scale(1), rotate(35deg);

 transform: scale(1), rotate(35deg);

}


.pentagram::after {

 content: "";

 width: 0;

 height: 0;

 border-bottom: 70px solid #dc5947;

 border-left: 100px solid transparent;

 border-right: 100px solid transparent;

 -webkit-transform: rotate(-70deg);

 -moz-transform: rotate(-70deg);

 -ms-transform: rotate(-70deg);

 -o-transform: rotate(-70deg);

 transform: rotate(-70deg);

 position: absolute;

 top: 0px;

 left: -100px;

}


.pentagram::before {

 content: "";

 width: 0;

 height: 0;

 border-bottom: 80px solid #dc5947;

 border-left: 30px solid transparent;

 border-right: 30px solid transparent;

 -webkit-transform: rotate(-35deg);

 -moz-transform: rotate(-35deg);

 -ms-transform: rotate(-35deg);

 -o-transform: rotate(-35deg);

 transform: rotate(-35deg);

 position: absolute;

 top: -45px;

 left: -60px;

}

5.水滴



<div class="drop"></div>

.drop::after {

 content: "";

 position: absolute;

 width: 30px;

 height: 20px;

 border-radius: 50%;

 background-color: #ace3ff;

 margin: 100px auto;

 top: -50px;

 left: 25px;

 box-shadow: 5px 12px 4px #ace3ff, -5px 11px 4px #ace3ff, 0px 14px 4px #4d576e;

 -webkit-transform: rotate(35deg);

}


.drop::before {

 content: "";

 position: absolute;

 width: 0px;

 height: 0px;

 border-style: solid;

 border-width: 0 40px 50px 40px;

 border-color: transparent transparent #ace3ff transparent;

 top: -30px;

 left: 10px;

}


.drop {

 width: 100px;

 height: 100px;

 border-radius: 50%;

 background-color: #ace3ff;

 position: relative;

 margin: 100px auto;

 box-shadow: 0px 6px 0 #3f475a;

}

6 绚丽流动边框





<div class="box-line1"></div>

.box-line2,

.box-line2::before,

.box-line2::after {

 position: absolute;

 top: 0;

 bottom: 0;

 left: 0;

 right: 0;

}


.box-line2 {

 width: 200px;

 height: 200px;

 margin: auto;

 color: #69ca62;

 box-shadow: inset 0 0 0 1px rgba(105, 202, 98, 0.5);

}


.box-line2::before,

.box-line2::after {

 content: "";

 z-index: 99;

 margin: -5%;

 box-shadow: inset 0 0 0 2px;

 animation: clipMe 8s linear infinite;

}


.box-line2::before {

 animation-delay: -4s;

}


.box-line2:hover::after,

.box-line2:hover::before {

 background-color: rgba(255, 0, 0, 0.3);

}


@keyframes clipMe {


 0%,

 100% {

   clip: rect(0px, 220px, 2px, 0px);

 }


 25% {

   clip: rect(0px, 2px, 220px, 0px);

 }


 50% {

   clip: rect(218px, 220px, 220px, 0px);

 }


 75% {

   clip: rect(0px, 220px, 220px, 218px);

 }

}


@keyframes surround {


 0%,

 100% {

   clip: rect(0px, 220px, 2px, 0px);

 }


 25% {

   clip: rect(0px, 2px, 220px, 0px);

 }


 50% {

   clip: rect(218px, 220px, 220px, 0px);

 }


 75% {

   clip: rect(0px, 220px, 220px, 218px);

 }

}


.box-line1:before,

.box-line1:after {

 position: absolute;

 top: 0;

 left: 0;

 bottom: 0;

 right: 0;

 content: "";

 z-index: 99;

 margin: -5%;

 animation: surround linear infinite 8s;

 box-shadow: inset 0 0 0 2px #69ca62;

}


.box-line1:before {

 animation-delay: -4s;

}


.box-line1 {

 border: 1px solid #69ca62;

 position: absolute;

 left: 500px;

 top: 200px;

 margin: auto;

 width: 200px;

 height: 200px;

 margin: auto;

}

7.Tooltip提示



<div class="tip" data-tip="CSS伪类">CSS伪类</div>

.tip::after {

 content: attr(data-tip);

 display: none;

 position: absolute;

 padding: 5px 10px;

 left: 15%;

 bottom: 100%;

 width: 150px;

 margin-bottom: 12px;

 transform: translateX(-50%);

 font-size: 12px;

 background: #000;

 color: #fff;

 cursor: default;

 border-radius: 4px;

}


.tip::before {

 content: " ";

 position: absolute;

 display: none;

 left: 15%;

 bottom: 100%;

 transform: translateX(-50%);

 margin-bottom: 3px;

 width: 0;

 height: 0;

 border-left: 6px solid transparent;

 border-right: 6px solid transparent;

 border-top: 9px solid #000;

}


.tip:hover::after,

.tip:hover::before {

 display: block;

}

8.CSS 伪类盒子阴影

使用伪元素:before and :after制作出了完美惊艳的相片阴影效果。其中的技巧是使用绝对定位固定伪元素,然后给它们的z-index一个负值,以背景出现。






<div class="box effect2">

   <h3>CSS 伪类盒子阴影</h3>

</div>

.effect2 {

   position: relative;

}


.effect2::before, .effect2::after {

   z-index: -1;

   position: absolute;

   content: "";

   bottom: 15px;

   left: 10px;

   width: 50%;

   top: 80%;

   max-width: 300px;

   background: #777;

   -webkit-box-shadow: 0 15px 10px #777;

   -moz-box-shadow: 0 15px 10px #777;

   box-shadow: 0 15px 10px #777;

   -webkit-transform: rotate(-3deg);

   -moz-transform: rotate(-3deg);

   -o-transform: rotate(-3deg);

   -ms-transform: rotate(-3deg);

   transform: rotate(-3deg);

}

.effect2::after {

   -webkit-transform: rotate(3deg);

   -moz-transform: rotate(3deg);

   -o-transform: rotate(3deg);

   -ms-transform: rotate(3deg);

   transform: rotate(3deg);

   right: 10px;

   left: auto;

}

CSS Box 阴影效果


9.Tabs当前激活状态



   <div class="sm-box flex">

       <div class="menu-tabs active">首页</div>

       <div class="menu-tabs">新闻</div>

       <div class="menu-tabs">视频</div>

       <div class="menu-tabs">图片</div>

   </div>

.menu-tabs {

 display: block;

 padding: 0.25rem 1.5rem;

 clear: both;

 font-weight: 400;

 color: #212529;

 text-align: inherit;

 white-space: nowrap;

 background-color: transparent;

 width: 50px;

 border: 0;

 height: 35px;

 justify-content: center;

 display: flex;

 cursor: pointer;

}


.menu-tabs:hover {

 color: #20a884;

 position: relative;

}


.menu-tabs:hover:after {

 position: absolute;

 content: "";

 border: 1px solid #20a884;

 width: 3rem;

 left: 0;

 bottom: 0;

 margin-left: 50%;

 transform: translateX(-50%);

}


.active {

 position: relative;

 color: #20a884;

}


.flex {

 display: flex;

}


.active::after {

 position: absolute;

 content: "";

 border: 1px solid #20a884;

 width: 3rem;

 left: 0;

 bottom: 0;

 margin-left: 50%;

 transform: translateX(-50%);

}

10.伪元素模糊背景



<div class="container">

  <div class="overlay">

     <h1>A blurred overlay</h1>

    <p>... mask or whatever

    <br>that is responsive and could be cross-browser compatible back to IE9</p>

  </div>

</div>

.container {

 width: 100%;

 height: 100%;

 margin: 0;

}


.container,

.overlay:before {

 background: url(https://wow.techbrood.com/assets/landing.jpg) no-repeat fixed 0 0 / cover;

}


.container {

 -webkit-box-align: center;

 -webkit-align-items: center;

 -ms-flex-align: center;

 align-items: center;

 display: -webkit-box;

 display: -webkit-flex;

 display: -ms-flexbox;

 display: flex;

 -webkit-box-pack: center;

 -webkit-justify-content: center;

 -ms-flex-pack: center;

 justify-content: center;

}


.overlay {

 max-height: 200px;

 margin: 0 auto;

 max-width: 768px;

 padding: 50px;

 position: relative;

 color: white;

 font-family: "Lato";

 position: relative;

 text-align: center;

 z-index: 0;

}


.overlay:before {

 content: "";

 -webkit-filter: blur(100px);

 filter: blur(100px);

 height: 100%;

 left: 0;

 position: absolute;

 top: 0;

 width: 100%;

 z-index: -1;

}

11.蓝湖文字



<span class="lanhu_text">

    本站由叫我詹躲躲提供技术支持

</span>

.lanhu_text {

 position: relative;

 color: #2878ff;

}


.lanhu_text::before {

 content: "";

 width: 80px;

 height: 20px;

 position: absolute;

 left: -86px;

 top: 0;

 background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAABCAYAAABJwyn/AAAAjElEQVQoU22NSw7CQAxDX8ahICF2HIDTcf9d1c8kaDpthQSL6CmxHRuk8cZfMxqf6DGh+Y5uCxquaB7xdUCXilZHWvBorEiOaqAKrkRRUEmUOw283TKRRb9b4GnIEpWmGYrA237kDh1w6J5N7zzzZv13gtuvT7t++jefUTYmwvpk7v3fPaCzn//9LfsBvRpHnliu+xMAAAAASUVORK5CYII=) 0 no-repeat;

}


.lanhu_text::after {

 content: "";

 width: 80px;

 height: 20px;

 position: absolute;

 right: -86px;

 top: 0;

 background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAABCAYAAABJwyn/AAAAhElEQVQoU22OSw7CQAxDXzJDWbHiEFyRJQflFggJSjyDph9oC6snx45lO52rrKJqzIyNlkFUPv6vnv+3uW9vrPpBq77/uWFPAXnrnfYVQ16JiWq0stjvxKB9vDc/MeaXWo5cqDEFUkKpEuEoB3ollHvUZ9QV4rFD3R3d9ujwJK5HxAW9AStemAIOwCNlAAAAAElFTkSuQmCC) 100% no-repeat;

}

12 主要标题



<div class="first-title">服务项目</div>

.first-title {

 position: relative;

 color: #a98661;

 font-weight: 400;

 font-size: 30px;

 text-align: center;

}


.first-title::before,

.first-title::after {

 position: absolute;

 content: "";

 width: 110px;

 border-bottom: 1px solid #a98661;

 top: 50%;

 transform: translateY(-50%);

}


.first-title::before {

 left: 100px;

}


.first-title::after {

 right: 100px;

}

13.鼠标浮层遮罩浮层



<div class="black-mask"></div>

.black-mask {

 position: relative;

 height: 100%;

 width: 100%;

 cursor: pointer;

}


.black-mask:hover {

 transition-duration: 1s;

 scale: 1.02;

}


.black-mask:hover:before {

 object-fit: cover;

}


.black-mask:hover:after {

 height: 100%;

 opacity: 1;

 transition-duration: 1s;

 display: flex;

 align-items: flex-end;

 padding: 0 30px 15px;

}


.black-mask::before {

 position: absolute;

 content: "";

 background: url(https://dcdn.it120.cc/2019/11/14/f17c5848-6d1f-4254-b3ba-64d3969d16b6.jpg) no-repeat;

 background-size: 100% 100%;

 width: 100%;

 height: 100%;

}


.black-mask::after {

 position: absolute;

 content: "雾在微风的吹动下滚来滚去,像冰峰雪山,似蓬莱仙境,如海市蜃楼,使人觉得飘然欲仙。山河景色在雾的装点下,变得更加美丽。远处的七连山巍峨挺拔,它们仿佛成了神仙住的宝山,令人神往。近处池塘边时时飘来雾气,在初升阳光的照耀下,呈现出赤、橙、黄、绿、青、蓝、紫七种色彩。......";

 width: 90%;

 height: 0%;

 bottom: 0;

 right: 0;

 z-index: 32;

 background: rgba(0, 0, 0, 0.3);

 opacity: 1;

 color: #fff;

 opacity: 0;

 padding: 0 30px 0;

}

14.绚丽光圈



<div class="aperture">光圈</div>

.aperture {

 width: 136px;

 height: 136px;

 background-color: #dc5947;

 border-radius: 50%;

 line-height: 136px;

 text-align: center;

 color: #fff;

 font-size: 24px;

 cursor: pointer;

 position: relative;

}


.aperture::before {

 border: 3px dashed #a0ff80;

 content: "";

 width: 144px;

 height: 144px;

 position: absolute;

 border-radius: 50%;

 left: -8px;

 top: -6px;

 animation: clockwise 5s linear infinite;

}


@keyframes clockwise {

 100% {

   transform: rotate(360deg);

 }

}

15.彩色流动边框



<div class="rainbow"></div>

.rainbow {

 position: relative;

 z-index: 0;

 width: 400px;

 height: 300px;

 border-radius: 10px;

 overflow: hidden;

 padding: 2rem;

}


.rainbow::before {

 content: '';

 position: absolute;

 z-index: -2;

 left: -50%;

 top: -50%;

 width: 200%;

 height: 200%;

 background-color: #399953;

 background-repeat: no-repeat;

 background-size: 50% 50%, 50% 50%;

 background-position: 0 0, 100% 0, 100% 100%, 0 100%;

 background-image: linear-gradient(#399953, #399953), linear-gradient(#fbb300, #fbb300), linear-gradient(#d53e33, #d53e33), linear-gradient(#377af5, #377af5);

 -webkit-animation: rotate 4s linear infinite;

 animation: rotate 4s linear infinite;

}


.rainbow::after {

 content: '';

 position: absolute;

 z-index: -1;

 left: 6px;

 top: 6px;

 width: calc(100% - 12px);

 height: calc(100% - 12px);

 background: white;

 border-radius: 5px;

}


@keyframes rotate {

 100% {

   -webkit-transform: rotate(1turn);

   transform: rotate(1turn);

 }

}

16.炫酷伪类边框



<div class="corner-button">CSS3</div>

.corner-button::before, .corner-button::after {

 content: '';

 position: absolute;

 background: #2f2f2f;

 z-index: 1;

 transition: all 0.3s;

}

.corner-button::before {

 width: calc(100% - 3rem);

 height: calc(101% + 1rem);

 top: -0.5rem;

 left: 50%;

 -webkit-transform: translateX(-50%);

 transform: translateX(-50%);

}

.corner-button::after {

 height: calc(100% - 3rem);

 width: calc(101% + 1rem);

 left: -0.5rem;

 top: 50%;

 -webkit-transform: translateY(-50%);

 transform: translateY(-50%);

}



.corner-button:hover {

 color: pink;

}

.corner-button {

 font-family: 'Lato', sans-serif;

 letter-spacing: .02rem;

 cursor: pointer;

 background: transparent;

 border: 0.5rem solid currentColor;

 padding: 1.5rem 2rem;

 font-size: 2.2rem;

 color: #06c17f;

 position: relative;

 transition: color 0.3s;

 text-align: center;

 margin: 5rem 12rem;

}

.corner-button:hover::after {

 height: 0;

}


.corner-button:hover::before {

 width: 0;

}

.bg-f2{

 background: #2f2f2f;

}

17.伪类美化文字



<div class="beautify-font" data-text='躲躲'>躲躲</div>

<div class="beautify-font2" data-text='躲躲'>躲躲</div>

.beautify-font{

 position: relative;

 font-size: 12rem;

 color: #0099CC

}

.beautify-font::before{

 position: absolute;

 font-size: 12rem;

 color: #333;

 content: attr(data-text);

 white-space:nowrap;

 width: 50%;

 display: inline-block;

 overflow: hidden;

 transition:1s ease-in-out 0s;

}

.beautify-font2{

 position: relative;

 font-size: 6rem;

 color: #0099CC

}

.beautify-font2::before{

 position: absolute;

 font-size: 6rem;

 color: #333;

 content: attr(data-text);

 white-space:nowrap;

 height: 50%;

 display: inline-block;

 overflow: hidden;

 transition:1s ease-in-out 0s;

}


.beautify-font:hover::before{

 width:0;

}

.beautify-font2:hover::before{

 height: 0;

}

18.照片堆叠效果

只使用一张图片来创造出一堆图片叠摞在一起的效果,能做到吗?当然,关键是要使用伪元素:before和:after来帮助呈现。把这些伪元素的z-index设置成负值,让它们以背景方式起作用。




<div class="stackthree"><img src="./images/city.jpg"></div>

.stackthree::before {

 background: #eff4de;

}


.stackthree, .stackthree::before, .stackthree::after {

 border: 6px solid #fff;

 height: 200px;

 width: 200px;

 -webkit-box-shadow: 2px 2px 5px rgba(0,0,0,0.3);

 -moz-box-shadow: 2px 2px 5px rgba(0,0,0,0.3);

 box-shadow: 2px 2px 5px rgba(0,0,0,0.3);

}


.stackthree::before {

 top: 5px;

 left: -15px;

 z-index: -1;

 -webkit-transform: rotate(-10deg);

 -moz-transform: rotate(-10deg);

 -o-transform: rotate(-10deg);

 -ms-transform: rotate(-10deg);

 transform: rotate(-10deg);

}

.stackthree::after {

 top: -2px;

 left: -10px;

 -webkit-transform: rotate(-5deg);

 -moz-transform: rotate(-5deg);

 -o-transform: rotate(-5deg);

 -ms-transform: rotate(-5deg);

 transform: rotate(-5deg);

}


.stackthree::before, .stackthree::after {

 background: #768590;

 content: "";

 position: absolute;

 z-index: -1;

 height: 0px\9;

 width: 0px\9;

 border: none\9;

}

.stackthree {

 float: left;

 position: relative;

 margin: 50px;

}

为元素的兼容性

不论你使用单冒号还是双冒号语法,浏览器都能识别。因为IE8只支持单冒号的语法,所以,如果你想兼容IE8,保险的做法是使用单冒号。

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

日历

链接

个人资料

蓝蓝设计的小编 http://www.lanlanwork.com

存档