墨韵

墨韵 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

墨韵 发布了文章 · 8月18日

纯新手向,vue+webpack+nginx项目从打包到上线流程

天下配置千千万,每个都不尽相同,下面只是我个人的配置方法;新手朋友仅作参考,如有错误希望指正,感激不尽!

在我们实际的项目开发中,可能有多个部署环境,测试环境,预发环境,正式环境。每个环境的请求URL都不同,每次打包时手动更换很麻烦,此时就需要配置多环境打包。

1,安装cross-env

npm install  cross-env --save

2,修改package.json

"scripts": {
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    //"build": "node build/build.js" 原本是这样的,我们需要注释这一句,使用下面我们的自定义命令
    "build:test": "cross-env env_config=test node build/build.js",
    "build:prod": "cross-env env_config=prod node build/build.js"
},

3,修改build.js

//1,注释
// process.env.NODE_ENV = 'production'
....//省略中间配置
const webpackConfig = require('./webpack.prod.conf')

//2,注释
//const spinner = ora('building for production...')

//这里是修改,读取设置的变量
const spinner = ora('building for ' + process.env.env_config);
spinner.start()

4,在config文件夹下新建test.env.js 和 prod.env.js 同理,设置不同的BaseURL

比如我的项目只需要3个环境,一个开发,一个本地测试(有可能你会遇到先把项目部署到你本地的ngx,方便别人使用),一个线上生产 ,下面只是一个示例

a,首先对dev.env.js修改

'use strict'
const merge = require('webpack-merge')
const dev = require('./dev.env')

module.exports = merge(dev, {
    NODE_ENV: '"development"',
    BASE_API: '"//192.168.31.164:8081"', // 这里就是配置开发环境接口的地方,在开发时候就是本地机器的地址,端口号就是你配置的webpack 端口号
})

b,对test.env.js 本地打包文件修改

'use strict'
const merge = require('webpack-merge')
const testEnv = require('./test.env');
module.exports = merge(testEnv, {
    NODE_ENV: '"test"', //这个test 对应的就是上面 "build:test": "cross-env env_config=test node build/build.js", 的test;
    BASE_API: '"//192.168.31.164:89"', // 这个是我要部署到本地ngx给别人使用的,ngx 配置的是89端口
})

c,对线上生产配置

'use strict'
module.exports = {
    NODE_ENV: '"production"',
    BASE_API: '"//mm.awsl.com"' // 配置生产地址 ,这个地址是上线后自己服务器的地址,端口号是你上线后开的nginx 监听端口号,如果这时候后台运维同事有给你配置好了的域名你就写他们给你的域名,没有给你就写这台线上服务器ip地址(是写外网ip,待会儿还要用到一个你同事服务器的内网ip)
}

5,修改webpack.prod.config.js

//const env = require('../config/prod.env') 注释掉原本这一句,使用下面的配置
const env = process.env.NODE_ENV = require(`../config/${process.env.env_config}.env`);

6,如果使用axios,那么在main.js 里面配置一下baseURl就行了


if (process.env.NODE_ENV == "development") { //如果是开发环境
  axios.defaults.baseURL = 'http:' + process.env.BASE_API + '/api';
} else { //线上生产环境
  axios.defaults.baseURL = 'http:' + process.env.BASE_API;
}

7,最后打包使用的时候 npm run build:test/prod就可以了

以下为线上nginx的配置,配置这个你需要准备2个工具,一个xftp(更新资源文件),一个xshell(操作服务器);向运维同事要到关于服务器的密码,登录名,然后在xshell 里面登录进去,或者你就让他们给你弄一下,总之先xftp和xshell都先连进去服务器

8,关于nginx的配置

一般来说nginx的只需要注意两点,一个是你ngx 项目的资源防止位置(路径),另一个就是请求代理转发的配置,
我们对ngx的配置都是在 nginx.conf 这个文件夹里面,如果你的服务器没有安装ngx 就先安装一下ngx;

以Ubuntu为例,运行

sudo apt-get update
sudo apt-get install nginx

即可安装ngx;默认情况下都是安装在/etc/nginx/这个路径下,我们主要要配置的nginx.conf 也在这个路径下;

9,配置ngx.conf文件里的 http 选项,我们的项目配置基本都在在这个选项里面; 如果你会vim的操作,你就可以直接在xhell直接里面写配置命令,如果你不熟悉的话可以直接在xftp 里面找到这个文件,右键,选择打开方式,使用你本地机器安装的编辑器去编辑这个文件,编辑完成ctrl+s 保存就行了,跟你使用vim是一样的

// ## 这是配置代理转发请求地址,这里写的是你要请求接口,翻上去看上面的第4点下面的c步骤,上面说到的还需要的同事线上服务器的内网ip 就是这个
upstream webServer{      
    server 172.21.16.158:8080;
}
server {             
    listen 80;
    charset utf-8;
    //这里一定和上面第4点下面的c步骤里面的`BASE_API` 保持一致,那里写的ip,你这里就写ip,那里写的域名,这里也一定写域名,否则就会报跨域错误
    server_name mm.awsl.com; 
    location / {
        root /var/www/html/myApp/dist/; //这里是放的是你的打包好的项目位置, 这段配置的意思就是访问mm.awsl.com:80就是指向这个目录
        index index.html;
    }
    //这段配置是转发代理,一般(至少我们是这样)后台请求接口都会有一个统一的前缀,比方'/web/aa','/web/bb','/web/cc' 这样的,这段意思就是把所有'/web/' 这样的请求都使用'webServer'配置去请求;其实这个配置就是跟你本地项目'/config/index' 里面的'proxyTable'一个意思;
    location /web/ {
        #include uwsgi_params;
        proxy_pass http://webServer;
    }
}

上面就是一个示范,如果没什么特殊需求,这样配置应该就可以了,至少我的是这样;

上面的配置都完成之后利用xshell重启一下ngx 就行了;

nginx -c /etc/nginx/nginx.conf -s reload

至此,你的整个前台打包上线流程完成;接下来就可以去浏览器里面打开mm.awsl.com了,如果你没配置域名,就用你配置的ip,也是一样的效果;

以上内容本地配置部分参考 原文地址

上面都是我第一次布置项目的全流程,希望对你有所帮助,如有错误,欢迎指正,感激不尽

查看原文

赞 3 收藏 3 评论 0

墨韵 赞了文章 · 5月8日

关于vue+element-ui项目的分页,返回默认显示第一页的问题解决

问题描述

当前页面如下
当前页面

然后点击页码跳到第3页,然后在第三页点击页面链接跳转到新的页面
页面点击跳转

然后在新页面点击返回按钮,返回到当前页,结果页面分页显示第一页,对应页面内容也是第一页
页面返回是第一页

期望效果

从新页面返回的时候,页码和页面内容仍旧保持在跳转离开前的样子。

解决办法

利用localStorage或者sessionStorage将跳转页面前的currentPage存储起来,然后,再次返回当前页的时候,在created生命周期里,获取到存储的currentPage,再进行加载

代码解释 我这里用的是sessionStorage,关于sessionStorage的使用,我这边先做下解释,以免后面看不懂。之前开发的时候我是先在全局定义了两个sessionStorage的方法,用于存取值

//给sessionStorage存值
setContextData: function(key, value) { 
    if(typeof value == "string"){
        sessionStorage.setItem(key, value);
    }else{
        sessionStorage.setItem(key, JSON.stringify(value));
    }
},
// 从sessionStorage取值
getContextData: function(key){
    const str = sessionStorage.getItem(key);
    if( typeof str == "string" ){
        try{
            return JSON.parse(str);
        }catch(e) {
            return str;
        }
    }
    return;
}

分页代码

分页代码

然后将currentPage在每次点击页码的时候存到sessionStorage里

点击分页跳转

这里解释下,qryTableData()是我定义的请求接口交易,刷新页面内容的方法。

然后在当前页的created生命周期里从sessionStorage里面取currentPage。

created

这样,我们再返回当前页的时候,页面内容将会是跳转离开前的样子。

但是至此工作仅仅完成一半,因为我们发现这个bug并没有完全解决
完成一半

问题造成原因
我们返回当前页面取得总条数totalNum的之前,element-ui的分页组件已经在页面加载完毕,当时的totalNum绑定的是data里面初始化的数据0,所以当总条数为0的时候,分页组件的页码默认为1。并且当totalNum在created生命周期里取得数据后,分页组件也不会刷新。所以这就导致, 页面内容正确,但是页码高亮依旧是第一页

解决办法
我们需要在created之后刷新这个分页组件或者让分页组件的html后于created之后加载到页面。
再次刷新这个分页组件是不现实的,我们选择在created之后加载分页组件。具体办法就是使用v-if。在totalNum不为data里面给的初始值0的时候,才让这段html加载到页面。

v-if

然后再次测试,发现完美解决问题。

perfect

后续:
这里为什么用的是v-if而不是v-show。这就是每个vue初学者需要明白的事情了,就是v-if和v-show的区别。嘿嘿😁

特别感谢@Deguang大神,在他的指导下,才踩出了这个element-ui的坑🙂

查看原文

赞 4 收藏 2 评论 1

墨韵 赞了文章 · 4月9日

nginx解决跨域问题

一. 产生跨域的原因

1.浏览器限制
2.跨域
3.XHR(XMLHttpRequest)请求

二. 解决思路

解决跨域有多重,在这里主要讲用nginx解决跨域

1.JSONP
2.nginx代理
3.浏览器禁止检查跨域

三. 下载安装nginx

nginx下载地址

  • 选择其中一个版本下载,再解压即可使用
  • 在nginx目录下输入nginx -v,若出现版本号,则安装成功
  • nginx

四. nginx反向代理解决跨域(客户端解决跨域)

1.我们使用jquery的ajax发送请求,node开启后台服务

前端代码:
利用jQuery的ajax api发送请求

    <button id="getOK">发送请求OK(客户端解决跨域问题)</button>
    <button id="getNO">发送请求NO(客户端解决跨域问题)</button>
    <script>
        $(document).ready(function () {
            $('#getOK').click(function () {
                $.ajax({
                    url:'http://localhost:3000/ok',
                    success:function(res) {
                        console.log("success",res)
                    },
                    error:function(err) {
                        console.log('fail',err)
                    }
                })
            })
            $('#getNO').click(function () {
                $.ajax({
                    url:'http://localhost:3000/no',
                    success:function(res) {
                        console.log("success",res)
                    },
                    error:function(err) {
                        console.log('fail',err)
                    }
                })
            })
        }) 
    </script>

后端代码:
利用node的express框架开启服务,并根据url返回json格式的数据,
设置这么多接口的目的是为了后面匹配nginx的location配置的

const express = require('express')
const cookieParser = require('cookie-parser')

var app = express()


var router = express.Router()
router.get('/ok',function (req,res) {
    res.json({
        code:200,
        msg:"isOK"
    })    
})

router.get('/ok/son',function (req,res) {
    res.json({
        code:200,
        msg:"isOKSon"
    })    
})

router.get('/ok2',function (req,res) {
    res.json({
        code:200,
        msg:"isOK2"
    })    
})

router.get('/no',function (req,res) {
    res.json({
        code:200,
        msg:"isNO"
    })    
})

router.get('/no/son',function (req,res) {
    res.json({
        code:200,
        msg:"isNOSON"
    })    
})

router.get('/no/son2',function (req,res) {
    res.json({
        code:200,
        msg:"isNOSON2"
    })    
})

app.use(router)
app.use(cookieParser)
app.listen(3000,function () {
    console.log('listen in 3000')
})

然后开启node服务
开启node服务
现在可以测试下接口
测试接口
可以看出,node服务成功开启
现在可以尝试不开启nginx服务直接发送ajax请求会出现什么情况
(注意:发送ajax请求需要以服务器方式打开网页,不能以文件形式)
跨域问题
如图,在5500端口请求3000端口出现了跨域问题,这时候就可以开启nginx服务并配置location进行解决

2.配置nginx进行反向代理解决跨域

反向代理的原理就是讲前端的地址和后端的地址用nginx转发到同一个地址下,如5500端口和3000端口都转到3003端口下,具体配置如下:

  • 打开nginx目录下的conf目录里面nginx.conf
  • 为了方便以后测试,我们将配置分离开来,弄成多个文件
  • 在nginx.conf的http对象的最后加上include ../vhost/test.conf;(注意要最后加上分号)
  • 这样就可以在test.conf下单独配置了

具体的location配置规则如下:
nginx的location配置规则

server
{
   listen 3003;
   server_name localhost;
   ##  = /表示精确匹配路径为/的url,真实访问为http://localhost:5500
   location = / {
       proxy_pass http://localhost:5500;
   }
   ##  /no 表示以/no开头的url,包括/no1,no/son,或者no/son/grandson
   ##  真实访问为http://localhost:5500/no开头的url
   ##  若 proxy_pass最后为/ 如http://localhost:3000/;匹配/no/son,则真实匹配为http://localhost:3000/son
   location /no {
       proxy_pass http://localhost:3000;
   }
   ##  /ok/表示精确匹配以ok开头的url,/ok2是匹配不到的,/ok/son则可以
   location /ok/ {
       proxy_pass http://localhost:3000;
   }
}

上面代码的意思是将localhost:3003转发为location:5500,也就是说现在访问localhost:3003实际上是访问location:5500,而访问localhost:3003/no则是访问localhost:3000,并以no开头的url

  • 现在我们可以开启nginx服务了,在nginx目录下使用start nginx即可开启服务

开启nginx服务

  • 每次修改配置都需要执行nginx -s reload命令才能生效

reload
现在修改前端代码,将之前请求的接口的端口换为3003,如下:

$('#getOK').click(function () {
                $.ajax({
                    url:'http://localhost:3003/ok',
                    success:function(res) {
                        console.log("success",res)
                    },
                    error:function(err) {
                        console.log('fail',err)
                    }
                })
            })

在浏览器访问的也不算location:5500,而是localhost:3003了,再次发送请求也不会出现跨域问题了,因为他们都是同一个域了,这就是nginx反向代理
成功

五. 后端配置nginx解决跨域(服务端解决跨域)

1. 依旧是ajax+node

这是前端代码

$(document).ready(function () {
            $('#get').click(function () {
                $.ajax({
                    url:'http://localhost:3002/ok',
                    //  带cookies的请求
                    xhrFields:{
                        withCredentials:true
                    },
                    success:function(res) {
                        console.log("success",res)
                    },
                    error:function(err) {
                        console.log('fail',err)
                    }
                })
            })
        })

后端代码同前面
还有nginx配置如下:


server
{
    listen 3002;
    server_name localhost;
    location /ok {
        proxy_pass http://localhost:3000;

        #   指定允许跨域的方法,*代表所有
        add_header Access-Control-Allow-Methods *;

        #   预检命令的缓存,如果不缓存每次会发送两次请求
        add_header Access-Control-Max-Age 3600;
        #   带cookie请求需要加上这个字段,并设置为true
        add_header Access-Control-Allow-Credentials true;

        #   表示允许这个域跨域调用(客户端发送请求的域名和端口) 
        #   $http_origin动态获取请求客户端请求的域   不用*的原因是带cookie的请求不支持*号
        add_header Access-Control-Allow-Origin $http_origin;

        #   表示请求头的字段 动态获取
        add_header Access-Control-Allow-Headers 
        $http_access_control_request_headers;

        #   OPTIONS预检命令,预检命令通过时才发送请求
        #   检查请求的类型是不是预检命令
        if ($request_method = OPTIONS){
            return 200;
        }
    }
}

发送预检命令的是非简单请求,具体可以看慕课网ajax跨域完全讲解
实际上不是非简单请求的且不带cookie只需2个字段即可解决跨域
add_header Access-Control-Allow-Methods *;
add_header Access-Control-Allow-Origin $http_origin;

  • 具体效果如下图:
  • 服务端跨域

这时只需改ajax请求的端口接口,无需修改前端服务器的地址

最后附上源码:
nginx解决跨域问题

查看原文

赞 44 收藏 33 评论 8

墨韵 赞了回答 · 3月14日

解决vue v-html 直接把<br/>输出出来了 ,我需要换行 怎么处理

给你的div加个white-space: pre-wrap;然后把<br/>换成正常的换行符 然后可以把那个replace(/\n/g, '<br/>')去掉了。

关注 4 回答 1

墨韵 发布了文章 · 2月4日

iframe父子传参通信

在最近的项目里面,用到了不少关于iframe父子传参通信的相关操作,记录一下,虽然很简单,但是确实十分有用的;
iframe通信可以分为2种,跨域和非跨域两种.分别说明;
有一点很重要,iframe是可以给name 属性的;给上name 属性可以省下一些代码;

非跨域 父调子

//父页面
<button class="b" id="b">点击</button>
<iframe data-original="a.html" name='child' id="f"></iframe>

<script>
    var ob=document.getElementById('b');
    var msg='hellow,i'm your father!!'
    ob.onclick=function(){
        if(child.document.readyState=="complete"){
            child.window.fnChild(msg); //关键
        }
    }
</script>

//子页面
<script>
function fnChild (arg) {
    console.log(arg); //确实得到了 hellow,i'm your father!!
}
</script>

父页面调用子页面使用 childFrameName.window.fnName();;当然有一点很重要,你需要判断iframe 里面的东西是否加载完成,如果加载未完成就调用的话是会报错的;
判断iframe 加载是否完成有2种方法
1,childFrameName.document.readyState=="complete"来判断;
2,childFrameName.onload=function(){} 使用onload 回调函数,把调用的方法都写在这个回调函数里面;

非跨域 子调父

//在父页面
<div id="a" class="a"></div>
<iframe data-original="a.html" name='child' id="f"></iframe>

<script>
    function changeColor(){
        var oDiv=document.getElementById('a');
        oDiv.style.backgroundColor="red";
    }
</script>

//在子页面
<button class="ob" onclick="c()">anniu</button>
<script>
    function c(){
        parent.window.changeColor(); //关键
    }
</script>

同样的,在子页面调用父页面的方法使用 parent.window.fnName()就可以了;

这种操作难免会遇到父页面获取子页面的元素,或者子页面获取父页面的元素进行操作;

非跨域 父页面获取子页面元素操作

首先,我们有几种方法拿到子页面的window对象或者doucument 对象,(还是使用上面的html)

//原生js 获取子页面window对象
1, var childWindow=document.getElementById("f").contentWindow;
2, var childWindow=document.getElementsByTagName('f')[0].contentWindow;
//其实也就是普通的获取方法,只是后面多了一个contentWindow;
//jquery
var childWindow=$('#f').contentWindow;

//获取子页面的document对象 (假设已经通过上面的方法得到了window对象)
var frameDoc=childWindow.document;
var frameBody=frameDoc.body;
//jquery 也是跟上面的一样
var frameDoc=$(childWindow.document);

//原生获取元素
childWindow.document.getElementById('a') //上面都已经拿到了子页面的window对象,所以获取子页面的元素也就只需要想普通操作那样获取就好
childWindow.document.getElementById('a').style.color='red' //改个颜色

//jq拿子页面元素
$('#f').contents().find('#a'); //$('#f').contents 这相当于拿到了iframe 里面所有的dom;

非跨域 子页面获取父页面元素

//原生js
window.parent.document.getElementById('a'); //window.parent获取到父页面的window对象,那么接可以使用一般操作获取元素
window.parent.document.getElementById('a').style.color="red";//dom操作
//jquery
$("#a",parent.document); //$(父页面元素选择器, parent.document);
$("#a",parent.document).css('border','1px solid red');

上面的是不存在跨域的情况,但是有时候会遇到跨域情况,在这次的项目里面就是出于跨域状态下,开始看了一些资料,说是在用一个iframe做中间层去做,但是太麻烦,在这里介绍一个十分还用的方法postMessage

postMessage

window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为https),端口号(443为https的默认值),以及主机 (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。

以上摘自MDN 原文 postMessage;

otherWindow.postMessage(message, targetOrigin, [transfer]);
//otherWindow 窗口对象
// message 传递的消息,可以是对象,可以是字符串
// target 目标窗口,* 代表所有;

postMessage十分强大,既可以子传父,也可以父穿子,并且可以突破同源限制

来看我遇到的使用场景;
我在主页面有个透明遮罩,里面是一个iframe的登录窗口,在子页面点击关闭的时候,需要关掉父页面的这个登陆遮罩;在这里存在跨域,所以使用上面的获取元素,操作元素的方法不能够使用,这里使用postMassage来做

//子页面
<div id="loginBox">登录窗口</div>
<div id="close"></div>

//父页面
<div id="loginMask">
    <iframe data-original="子页面"></iframe>
</div>

//子页面
<script>
    let oClose=document.getElementById('#close');
    oClose.onclick=function(){
        window.parent.postMessage('close','*');
    }
</script>
//父页面
<script>
    window.addEventListener('message',function(event){
        if(event.data=='close'){
            let oLoginMask=document.getElementById('loginMask');
            oLoginMask.style.display="none";
        }
    })
</script>

上面的代码其实很简单,在子页面里面获取了元素,该元素触发点击事件的时候,向父窗口发送一个消息,传递了一个消息;(这个消息参数会在接收页面的event.data查到);
在父页面监听message事件,监听到了就让登录遮罩消失;

父传子

同样,在父窗口也可以使用postMassage 来传递消息到子页面;

//父页面
<button id="btn">传递消息</button>
<iframe id='f' data-original="子页面.html"></iframe>
//子页面
<div id="a"></div>

//父页面
<script>
    let oBtn=document.getElementById('btn');
    let oFrame=document.getElementById('f');
    oBtn.onclick=function(){
        oFrame.contentWindow.postMessage('hello','*');
    }
</script>

//子页面
<script>
    window.addEventListener('message',function(){
        if(event.data=='hello'){
            document.getElementById('#a').innerText=event.data;
        }
    })
</script>
查看原文

赞 1 收藏 1 评论 0

墨韵 发布了文章 · 1月10日

一个tab切换

前面一章节简单介绍了js 里面创建对象的几种方式,其中每种都是有利有弊;但是最为安全有效的还是最后一个混合构造函数方式;

回顾一下这种方式的简单套路
1,利用构造函数加属性;2,利用原型加方法(事件函数);

现在我们来把一个普通的面向过程的函数改成面向对象的形式;用一个最简单的tab来说明

<style media="screen">
    button.act{
        background:red;
    }
    .box{
        width:400px;
        height:300px;
        color:white;
    }
    .box div{
        display:none;
        height:100%;

    }
    .box div.act{
        display:block;
    }
</style>
<div id="wrap">
    <div class="nav">
        <button type="button" class="act" name="button">选项一</button>
        <button type="button" name="button">选项二</button>
        <button type="button" name="button">选项三</button>
    </div>
    <div class="box">
        <div class="box-i act" style="background:black">选项一div</div>
        <div class="box-i" style="background:teal">选项二div</div>
        <div class="box-i" style="background:blue">选项三div</div>
    </div>
</div>

<script>
window.onload=function () {
    var op=document.getElementById('wrap');
    var aBtn=op.getElementsByTagName('button');
    var aBox=op.getElementsByClassName('box-i');

    for (var i = 0; i < aBtn.length; i++) {
        aBtn[i].index=i;
        aBox[i].index=i;
        aBtn[i].onclick=function(){
            for (var j = 0; j < aBtn.length; j++) {
                aBtn[j].classList.remove('act');
                aBox[j].classList.remove('act');
            }
            this.classList.add('act');
            aBox[this.index].classList.add('act');
        }
    }
}
</script>

没啥样式,结构也没啥,js部分也很简单(贼恶心);

现在我们来把这东西改成对象形式的,在改成面向对象的时候我们需要明确几点;

1,不能有函数嵌套,但是可以有全局变量;

2,把普通的函数改成构造函数

3,函数里面的变量变成属性

4,函数里面的方法改为对象的方法

明确了上面的几点来动手吧,先从简单的动手,把原来函数的变量改成属性和改成构造函数吧,从window.onload里面把这东西拿出去
为此我们需要一个构造函数来做

function swtab(id){
    this.oparent=document.getElementById(id);
    this.aBtn=this.oparent.getElementsByTagName('button');
    this.aBox=this.oparent.getElementsByClassName('box-i');
    for (var i = 0; i < this.aBtn.length; i++) {
        this.aBox[i].index=i;
        this.aBtn[i].index=i;
        this.aBtn[i].onclick=function(){
            for (var j = 0; j < this.aBtn.length; j++) {
                this.aBtn[j].classList.remove('act');
                this.aBox[j].classList.remove('act');
            }
            this.classList.add('act');
            this.aBox[this.index].classList.add('act');
        }
    }
}

上面做了2件事,第一把原本代码块用一个函数抽了出去,并且 把原来的变量改成了这个构造函数的属性;(变成了this.),也就是以前所有的变量都要使用this. 的形式了;

现在我们要用第4点把构造函数里面的嵌套函数也抽出去,别忘了构造函数加属性,原型加方法原则

function swtab(id){
    this.oparent=document.getElementById(id);
    this.aBtn=this.oparent.getElementsByTagName('button');
    this.aBox=this.oparent.getElementsByClassName('box-i');
    for (var i = 0; i < this.aBtn.length; i++) {
        this.aBox[i].index=i;
        this.aBtn[i].index=i;
        this.aBtn[i].onclick=this.fnClick;
    }
}
swtab.prototype.fnClick= function(){
    for(var j=0;j<this.aBtn.length;j++){
        this.aBtn[j].classList.remove('act');
        this.aBox[j].classList.remove('act');
    }
    this.classList.add('act');
    this.aBox[this.index].classList.add('act');
}

现在没了函数嵌套,而是把click 函数挂在swtab的原型上;现在我们运行

在swtab.prototype.fnClick函数里面的for循环会得到 Uncaught TypeError: Cannot read property 'length' of undefined

这一句this.aBtn.length 引起了报错,为什么呢?在面向对象的时候,很多时候都是this引起的,要时刻警惕this到底指向哪儿去了,我们去原型方法(swtab.prototype.fnClick)里面console.log(this);

当我们点击了按钮之后 控制台输出了 <button type="button" name="button">选项二</button>,也就说现在this 指向了点击的那个按钮,仔细想一想并没有错,虽然这个fnClick 是属于swtab 实例对象的,但是这个时候调用他的并不是swtab 实例对象,而是被点击的那个按钮,一个按钮自然是没有length 属性了;

现在问题来了,我要怎么保持这个this不变,哪怕是按钮点击调用也保持不变;没错,这时候你应该想起了 var _this=this 这种奇怪的写法;对,我们使用闭包保持对this 的引用;使用闭包关键就是函数套函数,内层使用外层的变量

现在我们对调用函数在改变一下

var _this=this;
this.aBtn[i].onclick=function(){
    _this.fnClick();
};

现在我们在看一下 fnClick 函数里面的this指去哪儿

swtab {oparent: div#wrap, aBtn: HTMLCollection(3), aBox: HTMLCollection(3)}

可以看到,现在这个this 被保持引用了swtab 实例对象了,也没有报length的错误了;但是我们却得到了一个新的错误 Uncaught TypeError: Cannot read property 'add' of undefined
看来是fnClick 函数最后两句报错了; 很显而易见的错,我们一开始就是把this 定为当前点击的按钮,但是现在这个函数里面的this 被闭包锁定了swtab 实例对象,当然classList 属性不存在了;

这就难受了,这个时候fnClick函数里面this同时存在2个状态,一个要指向实例对象,另一个是要指向当前点击按钮...如果我们能够从外部获取到当前点击的按钮那就好办了,所以我们可以在外部传入当前点击的按钮...

现在在改造一下

var _this=this;
this.aBtn[i].onclick=function(){
    _this.fnClick(this);
};


swtab.prototype.fnClick= function(cur){
    var _btn=cur;
    for(var j=0;j<this.aBtn.length;j++){
        this.aBtn[j].classList.remove('act');
        this.aBox[j].classList.remove('act');
    }
    _btn.classList.add('act');
    this.aBox[_btn.index].classList.add('act');
}

现在在试一下,函数正常运行;
最后完成的代码

window.onload=function (argument) {
    new swtab('wrap');
}

function swtab(id){
    this.oparent=document.getElementById(id);
    this.aBtn=this.oparent.getElementsByTagName('button');
    this.aBox=this.oparent.getElementsByClassName('box-i');
    var _this=this;
    for (var i = 0; i < this.aBtn.length; i++) {
        this.aBox[i].index=i;
        this.aBtn[i].index=i;
        this.aBtn[i].onclick=function(){
            _this.fnClick(this);
        }
    }
}
swtab.prototype.fnClick= function(cur){
    var _btn=cur;
    for(var j=0;j<this.aBtn.length;j++){
        this.aBtn[j].classList.remove('act');
        this.aBox[j].classList.remove('act');
    }
    _btn.classList.add('act');
    this.aBox[_btn.index].classList.add('act');
}

现在总结一下,上面的东西看起来简单,但是里面的东西却是不少,既有对象的混合创建,又有闭包保证this 引用,还有this传递,从中也可看到,出的问题基本都是在这个this 的指向上,所以这个this 确实及其重要

查看原文

赞 0 收藏 0 评论 0

墨韵 发布了文章 · 1月9日

js 对象创建的几种方法

对象在所有编程语言中都是核心内容在js中他的创建方式也是多种多样,每一种创建方式都有其利弊,下面简单总结一下;

Object 内置对象创建

最简单的两种方式之一,直接利用系统内置的Object 来创建,系统内置Object 只有很少的方法属性,用它创建十分安全

var person1=new Object();
person.name='xiaoming';
person.age='18';
person.sayName=function (){
    alert(this.name)
}

var person2=new Object();
person.name='xiaohong';
person.age='16';
person.sayName=function (){
    alert(this.name)
}

person1.sayName();//xiaoming
person2.sayName();//xiaohong

这种方式优缺点很明显,优点是简单,不需要额外的代码,直接创建就好,方法属性都是挂在自己身上;缺点也很明显,这东西一次只能创建一个,不适合批量创建,假设遇到重复创建那就让人崩溃;

字面量{}创建

这个跟上面的利用Object 内置对象创建是一个意思,本质也就是一样,{}这个东西也就是Object 的语法糖而已;

var person1={
    name:'xiaoming',
    age:'18',
    sayName:function(){
        alert(this.name);
    }
}
person.sayName();


var person2={
    name:'xiaohong',
    age:'16',
    sayName:function(){
        alert(this.name);
    }
}
person1.sayName();

person2.sayName();

优缺点不用说,跟上面就是一个玩意儿;

有了上面的缺点自然就要想办法克服,对于相同的东西人们喜欢抽出来,相似的就集合封装,于是有了这种东西

工厂方式

function ceartPerson(name,age){
    var person={
        name:name,
        age:age,
        sayName:function(){
            alert(this.name)
        }
    }
    return person;
}
var person1=ceartPerson('zhangshan',18);
person1.sayName();//zhangshan

var person2=ceartPerson('lishi',20);
person2.sayName(); //lishi

我们创建了一个函数用来返回所需要的对象,把这个对象所需要的信息通过传参的方式传入来构造不同的对象;我们在函数ceartPerson里面有一个原料person对象,我们就是通过改变这个原料person对象的属性来构造一个个对象,这种作业方式很像工厂的流水线作业,外面来一个,里面加工一个,所以这种方式也被形象的成为工厂模式;

但是这种方式也是有自己的缺点的,第一个有些难为强迫症患者,就是没有new 关键字,对于人们创建对象的固有认知不一样;
第二个相对致命,一个神奇的事情

alert(person1.sayName===person2.sayName) //false;

这就很郁闷了,这代表着什么呢,这就意味着从这个工厂里面出来的对象每一个都是独立个体,互不影响,他们之间的方法并不是引用同一个地方,但是明明功能都是一样的,所以这种方式会极大的消耗我们的系统资源,这显然不是我们乐意看到的

第三个缺点,无法使用instanceof 来判断自己的来历;在前面提到过instanceof 可以用来判定 对象的隐式原型和目标的原型对象是否在一条原型链上(更加官网的说法 instanceof运算符用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置),现在使用工厂模式创建的对象却无法使用instanceof,这就导致原型链混乱,这显然也不是我们乐意看到的;

var obj={};
console.log(obj instanceof Object) //true

console.log(person1 instanceof ceartPerson) //false;

构造函数

function CreatPerson(name,age){
    this.name=name;
    this.age=age;
    this.sayName=function(){
        alert(this.name)
    }
}

var person1=new CreatPerson('zhangshan',18);
var person2=new CreatPerson('lishi',16);

person1.sayName(); //zhangshan;

构造函数,主语是函数,所以这种东西也就是一个函数,跟其他函数没什么区别,只是作用有点儿特殊而已,是用来创建对象而已,就像医生也还是一个人,只是工作是救人,所以我们管他叫医生,管教书育人的人叫老师,所以函数该有的操作构造函数都有;
这种做法比上面的工厂模式略有简化,首先在构造函数内部并灭有显式的创建对象,也没有显示的返回对象这一步,
所有的操作都在外部的new 关键字做了
首先new 会在构造函数里面偷偷的创建一个对象,然后使用call 方法转换this 指向为该对象,然后在偷偷的返回该对象;当然这只是简单描述构造函数的创建过程;

console.log(person1 instanceof CreatPerson) //true
console.log(person1.sayName===person2.sayName) //false;

结果很明显,每一个出来的都是单独的实例对象,方法还是没有共同,同样存在了上面的问题,但是他却解决了原型链的问题,可以使用instanceof 来判定他的来源;

原型模式

前面提到的原型链顶端的存在,会让这条原型链下面的存在都具有相同的方法和属性,我们可以利用这个特性来创建对象试试

function CreatPerson(){};//创建一个构造函数,主要来利用这个玩意儿产生原型对象;
CreatPerson.prototype.name='zhangshan';
CreatPerson.prototype.age='18';
CreatPerson.prototype.sayName=function(){
    alert(this.name)
}
var person1=new CreatPerson();
person1.sayName();//zhangshan;

var person2=new CreatPerson();
person2.name='lishi';
person2.sayName(); //lishi;

alert(person1 instanceof CreatPerson) //true;
console.log(person2.sayName===person1.sayName) //true;

结果证明 从Person出来的实例都是对他的高度引用,新对象实例的属性和方法可以重写覆盖;这样做到个性化的操作;所有的实例均能共享同样的原型对象,所以也就解决了重复创建的问题,也解决了原型混乱的问题;但是缺点也很明显,没办法初始化参数,用上面的例子说明就是所有从CreatPerson 出来的对象实例的name 属性初始化都是zhangshan,这是相当危险的操作;

混合构造模式

既然构造函数可以初始化参数,原型模式可以搞定原型链混乱,能不能想办法综合一下呢?

function CreatPerson(name,age){
    this.name=name;
    this.age=age;
}
CreatPerson.prototype.sayName=function(){
    alert(this.name)
}
var person1=new CreatPerson('zhangshan',18);
person1.sayName()//zhanshan

var person2=new CreatPerson('lishi',16);
person2.sayName()//lishi

console.log(person1 instanceof CreatPerson); //true
console.log(person1.sayName===person2.sayName);//true

可以看到这种原型混合构造模式能够有效的解决创建对象过程的问题,使用构造函数加属性,使用原型加方法;这样既能搞定初始化参数的问题,也能解决方法重复创建的问题;他是一种较为稳妥的方法,缺点就是写着麻烦了点儿

查看原文

赞 0 收藏 0 评论 0

墨韵 发布了文章 · 1月9日

原型(Prototype),原型链(Prototype Chain)

玩js一定接触过这东西,就算是没怎么用过,但是肯定也是对这东西如雷贯耳;该明白这东西就能理解一些js中非常诡异的场景(ps:写这东西只是在学习过程中的一点儿记录)

起源图


如果你曾经在百度上搜索过原型或者prototype,那么一定看到过这图,当初一看到就头大,这tm画的是个啥,但是后面停下来仔细看一看,想一想,发现其实也没那么头大;

追根溯源

这个图的作用就是追根溯源,看这个图,我们只关注
2个关键点-->对象(Object)和函数(function)
2条主线-->原型(prototype)和隐式原型( --proto-- ),这里实际应该是 (两个下划线proto两个下划线),但是编辑器无法打出来,就把下划线用的中划线处理
然后记住2句话就行了;
1,原型属性只有函数才有,每个函数都有这prototype属性,这个属性的属性值指向这个函数的原型对象;这个原型对象默认就一个constructor 属性,属性值有指回去该函数
2,每个对象有个--proto--的隐式原型属性,结果指向创建该对象的函数的原型对象

先说个题外话,老人常说的一句话'龙生龙,凤生凤,老鼠的儿子会打洞',意思很简单,就是龙的后代一定是条龙,凤的后代也一定是只凤,而老鼠的后台则一定会继承打洞的能力,这个例子说明了血缘的重要性;血缘好比一条纽带联系起了先代和后代

关键点一: 函数(Function)和原型(prototype)

以图中那个叫做Foo的函数为例,Foo的prototype指向他的原型对象(强调一下,你不要去想这东西是不是有什么很特别,其实这东西就是一个对象,对象里面默认就一个叫constructor的属性) 用代码来看
看看原型对象里面有些啥

function Person(){
    console.log(1111)
};
console.log(Person.prototype);// {constructor: ƒ}

那么我们来看看图中的关系

function Foo(){};
console.log(Foo.prototype===Foo.prototype); //true
console.log(Foo.prototype.constructor===Foo); //true

这个例子说明第一句话的正确性;然后依据这个我们来看剩下部分,看到了吗,function Object 和 Object.prototype,function Function和Function.prototype,都形成了互指;这样一下这图是不是就看明白一大半了;

关键点二: 对象(Object)和隐式原型(--proto--);

这个属性是一个隐藏属性,js的设计者们都不希望开发者用到这个属性,很多js的开发工具都不带提示,而且打开f12都看到这个--proto--的颜色比较浅,但是没关系,直接写就好了
根据第二句话来看 每个对象有个--proto--的隐式原型属性,结果指向创建该对象的函数的原型对象
看例子

function Person(name){
    this.name=name;
    this.run=function(){
        console.log('我在跑...');
    }
}
var kate=new Person('kate');

现在我们新创建了一个kate出来,根据上面说的,我们得到了kate对象,那么kate对象是从哪儿来的呢?是从构造函数person来的,
好,我们来猜测一下对不对

console.log(kate.__proto__===Person.prototype);//true

结果验证了我们的猜想;
其实从这儿就可以大致感觉到prototype和--proto--的作用了,他们就类似于上面提到的血缘作用,对象的--proto--是用来向上寻找自己的出生地,而函数的prototype就是那个出生地;这一条条寻根朔源的道路就通过--proto--和prototype联系了起来,而这一根根的"血缘链条"就被称为原型链

对象可以通过构造函数出来,比方上面的kate对象,他是通过new 的Person,那么这个Person 从根本上来说又是来自什么地方呐?

Person.__proto__===??.prototype //??应该是创建Person的函数,那么他到底是谁

好,看图,在图中Foo就好比我们的Person,而Foo函数的--proto--指向了一个Function.prototype;好,现在又出现了一个新的东西,Function
事实上,所有的函数都是从Function这儿来的;
函数其实也可以这么写

function fn(a,b){
    return a+b;
}
console.log(fn(1,2));
//上面的是正常操作,下面的是反人类操作,根本不推荐
var fn1=new Function('a','b','return a+b');
console.log(fn1(5,6));

好知道了所有的函数都来自于Function,现在测试一下

Person.__proto__===Function.prototype //true;

函数!?对象;对象!?函数;

等等,好像有什么不对劲儿的地方... Person.prototype!? Foo.prototype!? Foo和Person不是函数吗?为什么会出现这种'.'的,只有对象才有的写法啊?而且既然有了prototype属性,我是不是还能给其他的属性啊?

Foo.a=1;
console.log(Foo.a);// 1

难道说函数也是个对象,好吧,事实确实如此;
现在还有几个问题;对象的创建方式里面有new Object()这种形式,看起来和new Person()完全一样啊,而Person是个构造函数,那难道说

console.log(typeof(Object)) //function;
console.log(typeof(Array)) //function;
console.log(typeof(Funtion)) //function;

是的,得到了一个事实:所有的对象都来自函数,是由函数所创建 (但是又别忘了函数也是个对象)
猜想:
既然Object 是函数,那么肯定有prototype,就得到 Object.prototype===Object.prototype;

 Object.prototype===Object.prototype // true;看起来有点儿搞笑,但是得到所有的对象的原型对象溯源一定到Object.prototype

同时,函数也是对象,那么肯定也有--proto-- ,指向创建它的函数的原型对象,创建他的函数是Function,那么肯定有

Object.__proto__===Function.prototype //true
Function.__proto__===Function.prototype //true

既然Function.prototype 是个对象,那么肯定也有--prototype--

Function.prototype.__proto__===Object.prototype //true

当把Object当作函数看 就会有 Object.prototype;那么他的--proto-- 是谁

 Object.prototype.__proto__===null //已经到了最初的起源之地了

最终,看明白那个图只需要记住开始的两点就好了;**所有从构造函数(自定义的构造函数也好,系统定义的Object,Array,Function也好)新创建出来的对象的--proto--一定指向这个构造函数的原型对象 而这个对象在向上那就是从 Object.prototype 出来的;
如果在向上那就到了起源nulll **

补充一些练习题

var F = function(){};
Object.prototype.a = function(){
    console.log('a');
};
Function.prototype.b = function(){
    console.log('b');
};
var f = new F();

问是否可以f.a(),f.b();

这个题还是考原型链,遵循instanceof 的法则,从本身出发,沿着--proto--向上走,看看能不能到目的地就行了;
首先看第一个,他在Object.prototype 上加了a属性,那么我们就看f能不能沿着--proto--找到Object.prototype;

//1,
f.__proto__===F.prototype //true;
//2,
F.prototype.__proto__===Object.prototype //ok,找到了,说明可以
//3,
f.a() //输出了'a';

然后来看第二个,其实在第一个就看到结局了,第一步最后都走到了Object.prototype,这已经是对象进化的终点了,在向上走就是null了

f.__proto__.__proto__.__proto__===null; //true;
f.b() // Uncaught TypeError: f.b is not a function

;
两条不同的路产生的原因还是 Object 走的函数路线还是对象路线;

在补充一个练习题

  var A = function() {};
  //A.prototype = {}; 原题是这样,但是方便说明,加一点儿东西
    A.prototype = { a:1 }; //1处 ,我们把这个对象叫做x

  var B = {};
  console.log(A.constructor); //为了方便,我们把这里叫做a行
  console.log(B.constructor); //b行

  var a = new A();
  //A.prototype = {};
    A.prototype = { b:2 }; //2处, 把这个对象叫做y

  var b = new A();
  b.constructor = A.constructor;
  console.log(a.constructor == A); //c行
  console.log(a.constructor == b.constructor); //d行
  console.log(a instanceof A); //e行
  console.log(b instanceof A); //f 行

在此之前,请记住一点:constrcutor 为对象的构造函数对象,存在于prototype对象(原型对象)中, 只要不对prototype对象重新复制,constructor都指向构造函数自身,也就是说,如果不去改动,那么一切都是正常的;如果一旦改动,那么constructor就将消失;

1,首先a,b两行应该比较简单,A是一个普通的函数,普通函数都是来自于Function,所以A的构造函数就是function Function(或者说Function);
B 是一个普通对象,对象不必说,自然是来自于 function Object();所以创建B的就是Object;
2,题中出现了2次A.prototype = {};这里一定小心,这2个{}可是指向了2个不同地方;是两个不同的对象,为了方便实验,我们在里面加一点儿东西来做验证;
在 第一个地方我们添加 A.prototype = { a:1 };在第二个地方添加 A.prototype = { b:2 };
3,本来对象a是从构造函数A里面出来的,那么说明c行肯定是正确的;但是在1处进行了原型对象重新赋值,这个时候A.prototype的constructo就已经没了;我们可以打印出在赋值之前的A.prototype看看;

console.log(A.prototype) //{constrcutor:f}

然后我们看看赋值之后的情况

console.log(A.prototype) //{a: 1}

这个时候看到A的原型对象的constructor已经没了,原型对象被重新覆盖了;
那么这时候回过头来看c行,既然A的prototype被覆盖,也就说明a无法再从这里继承constructor了,既然爸爸没了,那就去找爷爷吧...! a总归是一个对象,既然是对象,那么肯定是从Object函数出来的,所以猜测一下

a.constructor===Object; //true;

4,再来看d行,前面已经验证,a.constructor===Obeject,现在只需要看看b.constructor是否等于Object了;

b.constructor = A.constructor;

这句话是关键,这里把b的constructor重新指向了A.constructor,A是个什么,是个函数啊,那么函数从哪儿来的啊?肯定是 Function 嘛,所以

A.constrcutor===Function //true;

那么说明d行肯定不对了;
5,再来看看e行,请注意2个细节,a是在第一次A.prototype重新赋值之后实例化的,b是在第二次重新赋值之后实例化的,这两次看起来完全一样,但是这两个{}确实是 2个完全不一样的对象,所以我们加上了东西验证,如果不加验证第一眼看过去就会是这样的

A.prototype = {};
A.prototype = {}; //实际上这两个{}完完全全不一样啊;

粗心的小伙伴可能就爆炸了;
在明白了这一点之后,我们再来看e行,instanceof 的原则不变,

alert(a.a) //1;
alert(a.b) //undef
a.__proto__===A.prototype; // false;请注意a的实例化时间,他是在第一次修改A.prototype之后在实例化的(1处),虽然这时候a.constructor不在继承A.prototype.constructor,但是它总归是从A构造函数出来的,所以A还是a的爸爸;
也就是说这时候a也有了a属性了,请记住,这时候a的__proto__已经固定指向对象x所在的内存空间了!

接下来在2处进行了第二次覆盖,把A.prototype指向了y所在的空间,但是因为a是在这句话之前创建的,所以他并不知道这个更改,他还是继承的x所在空间,所以这句话是错误的;

然后在2处之后创建的b知道这次更改,他就被引导向了y的内存空间,所以
b.__proto__===A.prototype //true;
alert(b.b) //2;
alert(b.a) // undef

打个比喻就是a是A和前妻的孩子,b是A和现任生的孩子;

查看原文

赞 0 收藏 0 评论 0

墨韵 发布了文章 · 1月9日

我的插件整理

在日常工作中会用到很多的插件,他们极大的方便了我们的日常开发,可以快速的使我们实现一些漂亮的效果,现在把在工作中经常用到的插件进行整理

滑动swiper

Swiper常用于移动端网站的内容触摸滑动
Swiper是纯javascript打造的滑动特效插件,面向手机、平板电脑等移动终端。
Swiper能实现触屏焦点图、触屏Tab切换、触屏多图切换等常用效果。
Swiper开源、免费、稳定、使用简单、功能强大,是架构移动终端网站的重要选择!

滑动插件可以说是最经常用到的插件之一了,常见的滑动效果可以说都可以用这个插件做出来,因为swiper的使用场景太过于广泛,目前我只能列出我遇到过的场景;当然你也可以点击这里查看swiper基本效果,基本主流的滑动效果都可以在这里找到,其他的非主流效果就要见招拆招了(o゚▽゚)o (笑)

swiper目前有swiper3,swiper4两个版本,两者api都可以在官网找到,两个版本在某些地方做出了变化,但是其基本使用都是实例化swiper的时候传入id以及可选参数构成;这里使用版本为swiper4
发生变化的地方
;
可以看到变化的地方不多,主要是自动播放,分页器,回调函数发生了变化,下面通过一些项目中遇到的地方简单讲解一下,
在讲解之前首先先看一下swiper4 使用说明,对这个东西的使用有个大概认识;

其中有几个东西比较重要:
1,callback 回调函数,一般是滑动结束前后触发的函数
2,properties 整个swiper对象所有的属性
3,methods 在外部对swiper做的操作
4,slides grid 对swiper-slide 的布局设置
5,scroll-bar 对滚动滑块的设置
你要对这个swiper做什么操作从这三个里面去找一定可以找到对应的东西,下面开始介绍我遇到的使用场景

使用场景一 外部控制切换


这种使用场景很常见,主要就是里面两张图片既可以手指左右滑动切换,也可以借由上面两个按钮点击切换;
图片切换的时候上面的按钮也会跟着切换激活样式;
html结构以及代码

其实结构不必完全照这个来,你爱怎么写怎么写,这东西跟结构没有太大关系;
实例化js代码

//注意了,这是swiper3的写法
var skillSwiper = new Swiper('.skill-model-swiper', { //实例化滑动对象
    pagination : '.swiper-pagination-skill', //分页指示器
    onSlideChangeEnd: function(swiper) { //滑动结束获取当前激活序号,传入的swiper为当前的swiper对象,注意看上面的那张对比图的回调函数,这是一个区别
        var curidx = swiper.activeIndex; //activeIndex关键属性,当前swiper激活元素的序号,在api的属性里面去找
        $('.skill-lenli-btn button').eq(curidx)
        .addClass('skill-lenli-btn-act').parent()
        .siblings().find('button').removeClass('skill-lenli-btn-act');
    }
});

//看好了,这是swiper4的写法
var skillSwiper=new Swiper('.skill-model-swiper',{
    pagination: {
        el: '.swiper-pagination-skill'
    },
    on:{ //swiper4新增绑定方法,跟jquery的on性质差不多,用来添加回调函数或者事件句柄
        slideChangeTransitionEnd:function(){ //此方法和上面的onSlideChangeEnd作用一样,另外请注意这里并没有传入任何参数
            var curidex=this.activeIndex;//看到这个this了嘛,她就代指这个swiper对象,是不是和上面图片的写法一样了?
            $('.skill-lenli-btn button').eq(curidx).addClass('skill-lenli-btn-act')
            .parent().siblings().find('button').removeClass('skill-lenli-btn-act');
            }
        }
    })

上面的例子介绍了一个使用场景以及swiper3和4的一些区别,下面看第二个使用场景

使用场景二 tab切换, swiper显示隐藏, 滑动卡顿

这种使用是这是tab切换加上swiper,其实这种使用和一般的没什么区别,只是在一个区域有多个swiper存在,并且伴随tab标签切换,在切换显示隐藏的时候回出现一些意想不到的情况,就我遇到情况是只有第一个滑动正常,在我点击切换到后面swiper的时候,就会出现滑动不顺畅,以及滑不回去的情况(当然,这是我遇到的情况,有可能你会遇到其他的诡异情景);总之就是你通过一些外界操作影响了swiper内部的一些状态就可能会出现这些异常,这时候你需要监视器(查看文档 observer)来帮助了

observer
启动动态检查器(OB/观众/观看者),当改变swiper的样式(例如隐藏/显示)或者修改swiper的子元素时,自动初始化swiper。
默认false,不开启,可以使用update()方法更新
observeParents
将observe应用于Swiper的父元素。当Swiper的父元素变化时,例如window.resize,Swiper更新。

html结构以及代码

实例化js代码

$('.ds-sw>div').click(function(event) {
     var i = $(this).index();
     $(this).addClass('ds-sw-act').siblings().removeClass('ds-sw-act');
     $('.ds-person-item').eq(i).addClass('ds-person-item-act').siblings().removeClass('ds-person-item-act')
});
//这种3和4版本就没什么太大区别了,就pagination写法不一样而已
var dsItemSwiperA = new Swiper('.ds-person-swiper-a', {
    observer: true,
    observeParents: true,
    pagination : {
        el:'.dsItemSwiperASw'
    }
})

使用场景3 多图, 间隔, slide grid


这也是很常见的一种使用方法,在屏幕上显示一张主图,同时漏出上一张和下一张预览效果,在没有pagination分页器的情况下对于用户来说是一种很友好的提示方式
使用不难,关键在于css上

html结构以及代码

实例化js代码以及css部分

//css
.swiper-slide{
    width:296px;//最关键的一句,给每个swiper-silde给上宽度,
}
var dnrSwiper=new Swiper('.swiper-container-dinner',{
    slidesPerView: 'auto', //设置slider容器能够同时显示的slides数量(carousel模式)。可以手动设定,但是我个人不推荐手动设定,给auto就好,尤其是这种类似场景下
    centeredSlides: true,//默认fasle,设定激活slide的水平位置,即靠左;这个一般设定true,即位于swiper-wrapper中间
    spaceBetween: 10,//设置每一个swiper-slide的左右边距,单位px
    /*autoplay : true,不推荐直接这么写*/
    autoplay:{
        delay:3000,
        disableOnInteraction: false,//当用户操作swiper时候,是否停止自动轮播,默认为true,如果你直接写autoplay:true,那么这个值就是默认true,也就是当用户手动滑动或者点击了swiper之后,自动轮播将不再生效
        stopOnLastSlide:fasle//自动轮播到最后一个是否停止滑动,默认值为false;
    }
})

使用场景4 多图, 间隔, slide grid, scrollbar进度条

这种场景是滚动条的应用,在官方提供的demo里面有类似的;

这图有两个地方,第一个是第一个加载时候并不是贴边,也不是完全居中,第二个就是下面滚动条了
使用这种滚动条就不在指定pagination,而是使用scrollbar
html结构以及代码

css以及js代码

//css
.swiper-b{
    margin-top: 20px;
    height: 150px; //注意这个高度,swiper容器默认高度是里面swiper-slide的高度的,在下面有分页器的时候需要手动设定,分页器或者滚动条的位置默认是相对swiper-container距离底部3px;所以滚动条会和swiper-slide重合,所以你需要手动增大容器的高度,当然,这是根据设计图来说的;
}
.swiper-slide-b{
    width: 137px;
    height: 124px;
    background-color: pink;
    line-height: 124px;
    text-align: center;
    font-size: 16px;
}
.swiper-container-horizontal > .swiper-scrollbar{
    width: 95%; //手动更改滚动条的宽度
    transform: translateX(-50%); //居中显示滚动条
    left: 50%;
}
//js
var swiperB=new Swiper('.swiper-b',{
    slidesPerView: "auto",
    spaceBetween: 15,
    slidesOffsetBefore : 10,//设定slide与左边框的预设偏移量(单位px),这个实现不贴边效果
    slidesOffsetAfter : 10,//设定slide与右边框的预设偏移量(单位px)
    scrollbar: {
        el: '.swiper-scrollbar', //指定滚动
        dragSize: 30,//手动设定滑块的宽度,默认为'auto',他会自己算出来一个合适宽度,一般不需要设定
        draggable:true//设定是否可以手动拖动滚动滑块,默认为false不允许拖动
    },
});

使用场景5 双向控制,带3d的切换效果

;
这是起点3.3新版会员特权的介绍页详情页,两个难点,第一个介绍swiper的3d切换效果,第二个下面的介绍和上面的控制按钮是双向控制的;
首先第一个问题,3d切换效果,这个效果在swiper里面叫做coverflow,在api的特效选项里面,常用配置以及每个配置项是什么意思在介绍里面都有,待会儿上代码;
第二个问题,双向控制;这个在基础实例上也有案例,照着来也没问题,在api介绍里面的双向控制(controller)里面,有两种写法,一中是按照他的写法,在实例化swiper的时候写明control属性,谁控制谁,第二种写法是他这个control属性提出去,使用对象的方法

但是在使用过程中遇到两个问题,第一个是双向控制,上方swiper滑动方向和下方swiper的方向是相反的,但是我也没在control属性里面加上inverse(反向,这个属性在control里面也有介绍)属性,后面在修改发现,引起这个问题的原因是那个coverEffect的参数引起的,但是问题出现的原因暂时未知;
然后是第二个问题,上面的swiper里面11个slider,下面的也是,但是下面的一次只能显示一张,所以他可以滑动10次,但是上面的一次可以显示3个,所以只能滑动9次,因为在最后一次滑动就显示到了第9,10,11个,所以这时候就出现了异常问题;解决办法就是在第一个swiper上加上centeredSlides: true,属性;

最后js代码

var topSiwper=new Swiper(document.querySelector('.top-swiper-container'),{
    slidesPerView: 'auto',
    slideToClickedSlide: true,//一个可以不用让你手动写slideTo()的有用属性
    centeredSlides: true,//加上这个虽然感觉和设计图有些不一样,但是决绝了两边滑动相反的问题
});
/*下面的轮播组件*/
var centerSwiper=new Swiper(document.querySelector('.center-swiper-conatiner'),{
    slidesPerView: 'auto',
    effect:'coverflow',
    centeredSlides: true,
    spaceBetween:30,
    coverflowEffect: { //这个参数是调整到一个既有3d纵深效果,又解决了两边swiper滑动相反的问题
        rotate: 0 ,// 旋转的角度
        stretch: 0,// 拉伸   图片间左右的间距和密集度
        depth: 60,// 深度   切换图片间上下的间距和密集度
        modifier: 2,// 修正值 该值越大前面的效果越明显
        slideShadows : false// 页面阴影效果
    }
})
//双向控制
centerSwiper.controller.control=topSiwper;
topSiwper.controller.control=centerSwiper;

之前使用官方demo不行是因为版本问题,目前最新是在4.5.0+;下载最新版的就可以解决这个问题;

双向控制的进化版本

除了在要求两个swiper能够双向控制之外,还有一个需求就是预览的swiper点击当前一个还要自动向前移动一个slide的位置;这个在之前的加薪活动中有过,在照片集中又有应用

<style>
    #thumbs .swiper-slide{
        width: 1.8rem;
        height: 1.5rem;
        font-size: .24rem;
    }
    #thumbs .swiper-slide>div{
        width: 1.5rem;
        height: 100%;
        background-color: white;
        transition: all .25s linear;
        text-align: left;
        margin: 0 auto;
    }
</style>

<div class="swiper-container b" id="gallery"> <!--这个是内容swiper--> </div>
<div class="swiper-container t" id="thumbs" > <!--这个是预览swiper--> </div>

jQuery(document).ready(function($) {
    //导航swiper
    var galleryThumbs = new Swiper('#thumbs', {
        slidesPerView: 'auto',
        freeMode: true,
        watchSlidesVisibility: true,
        watchSlidesProgress: true,
        on:{
            init:function(){
                //导航字数需要统一,每个导航宽度一致
                navSlideWidth = this.slides.eq(0).css('width');
                clientWidth = parseInt(this.$wrapperEl.css('width')); //Nav的可视宽度
                navWidth = 0;
                for (i = 0; i < this.slides.length; i++) {
                    navWidth += parseInt(this.slides.eq(i).css('width'))
                }
            },
        }
    });

    //内容swiper
    var galleryTop = new Swiper('#gallery', {
        spaceBetween: 10,
        centeredSlides : true,
        slidesPerView: 'auto',
        on:{
            transitionStart:function(){
                activeIndex = this.activeIndex
                $(galleryThumbs.slides[activeIndex]).addClass('swiper-slide-active-custom').siblings().removeClass('swiper-slide-active-custom')
                //activeSlide距左边的距离
                navActiveSlideLeft = galleryThumbs.slides[activeIndex].offsetLeft
                galleryThumbs.setTransition(300)
                if (navActiveSlideLeft < (clientWidth - parseInt(navSlideWidth)) / 2) {
                    galleryThumbs.setTranslate(0)
                } else if (navActiveSlideLeft > navWidth - (parseInt(navSlideWidth) + clientWidth) / 2) {
                    galleryThumbs.setTranslate(clientWidth - navWidth)
                } else {
                    galleryThumbs.setTranslate((clientWidth - parseInt(navSlideWidth)) / 2 - navActiveSlideLeft)
                }
            }
        }
    });
    //绑定导航点击切换
    galleryThumbs.on('tap',function(e){
        clickIndex = this.clickedIndex
        clickSlide = this.slides.eq(clickIndex);
        $(clickSlide).addClass('swiper-slide-active-custom').siblings().removeClass('swiper-slide-active-custom')
        galleryTop.slideTo(clickIndex, 0);
    })
});

以上做法就可以满足要求,但是存在一个问题,这个东西对于预览swiper的布局有一些要求,在预览swiper,或者swiper-slide里面不能有间距,不管是css 写上去的margin或者是padding,还是js 设定的spaceBetween;这个都会导致计算错误(滑稽,别问我为什么);所以,遇到这种情况你可以用个折中的办法,把swiper-slide 弄宽一点儿,里面的内容用个div包一下,然后外层使用居中之类的办法处理

swiper-slide切换内部元素带动画

也是比较常见的点,达到这种效果需要借助swiper.animate.js,这个东西一般都在swiper的压缩包里面,没有的话自行下载一个;
使用方法:

<div class="swiper-slide swiper-out-slide swiper-no-swiping">
    <div class="animated ani swiper-out-slide-left">
        <img data-original="../jpg/b6.jpg" width="400" height="400" class="ani" swiper-animate-effect="fadeIn" swiper-animate-duration="0.5s" swiper-animate-delay="0.3s">
    </div>
    <div class="swiper-out-slide-right ani " swiper-animate-effect="fadeInUp" swiper-animate-duration="0.5s" swiper-animate-delay="0.3s">
        <h2>百度3</h2>
        <div><strong>纵里寻他千百度,默认回首,却在灯火阑珊处</strong></div>
        <div>百度百度百度百度百度百度百度百度百度</div>
    </div>
</div>
var swiperOut=new Swiper(document.querySelector('.swiper-out'),{
    effect : 'fade',//之所以使用这种效果是因为默认的slide效果感觉搭配动画会有点儿奇怪,当然,或许默认的slide效果搭配其他的animated动画会很不错
    on:{
        init: function(){
            swiperAnimateCache(this); //隐藏动画元素
            swiperAnimate(this); //初始化完成开始动画
        },
        slideChangeTransitionEnd:function(){
            swiperAnimate(this);
            //this.slides.eq(this.activeIndex).find('.ani').removeClass('ani'); //动画只展现一次,去除ani类名;ps:这一句很奇怪,如果放开这句话,那么每一张swiper-slide都不会消失的,会全部堆在页面上;
        }
    }
});

使用很简单,只需要在需要动画的元素上加上'ani' class,这是她内部提供的class,所以不能更改,然后你在加上swiper-animate-effect="fadeIn" swiper-animate-duration="0.5s" swiper-animate-delay="0.3s" 这些属性就好了,动画时间自由更改,动画类型也是自由更改;

制作匀速滚动效果,例如文字循环跑马灯

这个效果多用在一些列表文字展示,例如抽奖展示,在网上找到的基本都是3版本,其实跟4版本也差不多
html结构跟一般的结构没区别
实现这个效果主要是更改一点儿css效果,你可以把这段代码复制到你的css里面或者页面上;

.swiper-container-free-mode>.swiper-wrapper{
    transition-timing-function: linear; //之前是ease-in-out;
    -moz-transition-timing-function: linear; /* Firefox 4 */
    -webkit-transition-timing-function: linear; /* Safari 和 Chrome */
    -o-transition-timing-function: linear; /* Opera */
    margin: 0 auto;
}

js代码

var slideSwiper=new Swiper('.slidetext-swiper',{
    freeMode:true,//匀速滚动需要配合这个参数
    speed:3000, //这个参数主要配置配置滚动速度,默认的话滚得会很快,3s还不错
    autoplay:{
        delay: 0, //无延迟
    },
    loop:true, //,避免文字滚完了回滚看起来很蠢,这里注意,虽然官方文档说loop模式遇到freemode会引起抖动,但是在貌似并没有发现这种问题,可能是因为文字太小,间隔也小的原因看起来不明显吧;
    direction : 'vertical',
    noSwiping : true,
    slidesPerView:3,
})

滑动插件(better-scroll.js)

请注意很重要的一点,请务必小心
scroll实例化时候的选择器问题,一定要保证唯一性 也就说加入你是用的是jquery的class选择器,比如class为wrap 那么一定要使用$('.wrap')[0]来确保唯一性;或者你可以使用原生选择器方法 document.querySelector(selectors[, NSResolver]);
scroll滑动区域如果碰上了display:none; 那么极有可能会出现实例化了但是无法滚动的情况,因为display:none 的元素在页面上是没有宽高属性,只会得到0或者其他的什么稀奇古怪的值,所以插件只会拿到完全错误的宽高数据,那么就无法正确实例化, 实在不行你可以先把滚动区域的opacity设置为0,让插件至少拿到正确的宽高属性进行实例化,实例化完了在display:none;

最佳解决办法
使用他内置的refresh()方法,用这个方法你就可以不用设定样式了,什么透明度,zinde之类的,你只需要在点击显示遮罩的时候手动refresh()就好了

var bsw=cbs(); //创建bs 实例
$('.showbs').click(function(event) { //点击显示bs 弹窗
$('.option-mask').show();
bsw.refresh(); //手动刷新,强制计算bs实例
});

隐藏的时候什么都不用管,普通hide()就好

请注意内容的包裹元素和外层父元素的高度关系 实例化的那个dom元素的高度一定是固定的,而且必须小于内容的直系包裹元素,只有这样才能产生滚动落差,有时候你很容易实例化错误的dom对象;然后内容的直系包裹元素你不能有高度属性,他的高度就是里面内容高度;

多说一句,关于上面的display:none, 至少目前遇到2个问题是因为他引起的,一个是作品集那个pdf切换时候,第一个iframe默认显示,所以正确的显示了,但是第二个默认隐藏,当时就是写的display:none 后果就是导致他们的框架在计算的时候对第一张pdf加上了width:0; height:0; 这么一个该死的样式,结果搞了很久时间才解决;好在最后他们改版修复了这个bug;
第二个问题就是上面发现的那个better-scroll插件因为display:none 导致无法正确实例化的问题了

一个很好用的滑动插件,以下简称bs插件,通过模拟body滑动来克服ios 上uiwebview(比如手机QQ)等局部滑动异常的问题;
当元素内部高度大于包裹元素的时候就产生了滚动,滚动分为常规body滚动和局部滚动,body滚动底层由浏览器内核控制,不会受到太多影响;局部滚动相对麻烦,在ios上会产生许多问题;
直接介绍better-scorll使用方法,先放出使用说明better-scroll参数以及方法

使用bs插件要求dom结构一定简单,通常是一层包裹最佳,所有内容放在内层包裹里面

<div class="wrapper"> //请注意检查这东西和.content的高度,加入划不动你可以看看他们的css
  <div class="content">
    content...
  </div>
</div>

<script>
    var wrapper=$('#wrapper);
    var myScroll=new BScroll(wrapper,{ //直接实例化就好,参数可根据实际情况或查看使用说明添加
        HWCompositing:true,
        useTransition:false,  // 防止iphone微信滑动卡顿
        probeType: 3,
        click:true,
        wheel:false,
        snap:false,
    });
</script>

jquery.parallax.js 视差引擎

一款轻巧强大的视差引擎插件,可以做出背景元素跟随鼠标反向移动的炫酷效果,使用也很简单

<div id="container" class="container">
    <ul id="scene" class="scene">
        <li class="layer" data-depth="1.00"><img data-original="images/layer1.png"></li>
        <li class="layer" data-depth="0.80"><img data-original="images/layer2.png"></li>
        <li class="layer" data-depth="0.60"><img data-original="images/layer3.png"></li>
        <li class="layer" data-depth="0.40"><img data-original="images/layer4.png"></li>
        <li class="layer" data-depth="0.20"><img data-original="images/layer5.png"></li>
        <li class="layer" data-depth="0.00"><img data-original="images/layer6.png"></li>
    </ul>
</div>

js部分:

<script data-original="../deploy/jquery.parallax.js"></script>
$('#scene').parallax();

注意:元素上的 layer 类是一定不能丢掉的;data-depth决定景深,它还可以单独分开x,y方向写
<li class="layer" data-depth-x="-0.60" data-depth-y="-0.20"><img data-original="images/layer3.png"></li>
<li class="layer" data-depth="0.40" data-depth-y="-0.30"><img data-original="images/layer4.png"></li>

倒计时插件(countDown.js)

这是一个简单又实用的插件,直接拖出来一个倒计时效果,活动开始之前会自动提示敬请期待,结束之后显示活动结束
html结构

<div class="pmcount_down" style="color:#999;font-size:14px;">
    <em>剩余:</em>
    <span class="day_num">00</span>
    <span class="hour_num">00</span>:
    <span class="min_num">00</span>:
    <span class="sec_num">00</span>
</div>

js代码

$(".pmcount_down").countDown({
    startTimeStr: '2018/07/18 10:00:00', //开始时间
    endTimeStr: '2018/09/20 10:10:00', //结束时间
    daySelector:'.day_num',
    hourSelector: ".hour_num",
    minSelector: ".min_num",
    secSelector: ".sec_num"
});

注意
1, 当页面有多个定时器存在的时候,不要马虎全部复制粘贴,一定要注意每一个定时器里面pmcount_down,daySelector,hourSelector,minSelector,secSelector这几个class不能重复,要不然会实例化到同一个地方去,导致每一个倒计时得到一样的倒计时长,如果你看到几个倒计时是一样的倒计时长,那么看看是不是class重复了
2, 在ios里面要注意起始时间的格式,在pc,安卓时间戳可能是2018/07/18 10:00:00这个格式,但是在ios里面是2018-07-18 10:00:00;如果你的倒计时在pc上或是安卓上正常,但是到了ios上无法工作,那么就看看是不是这个时间格式问题

瀑布流加上图片加载(masonry.js+imagesloaded.js)

使用之前首先应该载入jquery,imagesloaded用于图片较多的时候方便masonry计算;两个插件加起来可以做一个漂亮的瀑布流,附上插件的使用参数设置 https://segmentfault.com/a/1190000007316788
一个简单的使用插件的起点后台demo

html结构以及css

.masonry {
    .item{
        float: left;
        position:relative;
        width:300px; //每一个item都限制宽度,这是一种等宽瀑布流,并没能实现等高的效果
        margin-bottom: 8px;
        &:hover .pic-info{ 
            bottom:4px; opacity: 1; 
        }
        img{
            width:100%; 
            max-width:100%;
            height:auto;//其实在后台的这个demo里面是做了溢出隐藏的,
        }
    }
}
//dom结构尽量简洁
<div class="masonry" id="masonry">
    <div class="item">
        <i class="delete">×</i>
        <img data-original="http://img2.imgtn.bdimg.com/it/u=3221513737,2348674445&fm=200&gp=0.jpg" alt="">
    </div>
    <div class="item">
        <i class="delete">×</i>
        <img data-original="http://img2.imgtn.bdimg.com/it/u=3221513737,2348674445&fm=200&gp=0.jpg" alt="">
    </div>
</div>

js部分

$(function() {
    var $container = $('#masonry');
    $container.imagesLoaded(function() { //这是imagesload.js提供的方法,链接说明也有关于他的说明
        $container.masonry({ //详细参数或者了解每一个参数的作用就查看上面的链接说明
            itemSelector: '.item',
            gutter: 15,
            isAnimated: true,
        });
    });
    
    $('.delete').click(function(event) {
        $container.masonry( 'remove', $(this).parent());//插件的事件,更多事件同样查看上面连接说明
        $container.masonry() //当容器内部发生了dom变化之后,建议都运行一次这个方法,让容器重新计算一次内部尺寸
    });
});

等高瀑布流

上面介绍的是一种等宽瀑布流,下面介绍一种等高瀑布流jquery.xgallerify,当然最好也配合上面的imagesloaded.js;

<div id="xgallerify" class="xgallerify photos">
    <div class="photo" style="display: inline-block; margin: 10px; width: 424.82px;">
        <img data-original="https://static.qidianla.com/qidian_modelbanner201911_20191120193147_6974.png">
    </div>
    ....省略一万个
</div>

js:

<script data-original="../assets/plug/pubuliu/imagesloaded.js"></script>
<script data-original="../assets/plug/pubuliu/jquery.xgallerify.min.js"></script>
$(function() {
    var $container = $('#xgallerify');
    $container.imagesLoaded(function() {
        $container.gallerify({
            mode: 'small',
            lastRow: 'adjust',
            debounceLoad: true,
        });
    });
});
参数默认值类型描述
margin10int各个图片之间的margin值
width800int图片画廊的宽度,单位像素
modeldefault, bootstrap, flickr 或 smallstring模式参数用于决定该图片画廊如何显示,以及每行显示多少张图片
lastRowadjust 或 fullwidthstring设置最后一行为全屏宽度或自动调整(建议使用自动调整)
jsSetup布尔布尔设置元素的默认CSS样式。
debounceLoad布尔布尔该参数会在渲染新的图片之前等待100毫秒,用于提高性能

拖拽插件(jquery.dad.js)

一个简单又实用的拖拽插件,可用于拖动排序
简单demo以及参数使用说明 拖拽插件使用说明
如果使用局部拖拽,那么拖拽的元素不能有visibity,至少目前他会影响拖拽...
一个官方例子

<div class="demo">
    <div class="item item1"><span>1</span></div>
    <div class="item item2"><span>2</span></div>
    <div class="item item3"><span>3</span></div>
    <div class="item item4"><span>4</span></div>
    <div class="item item5"><span>5</span></div>
</div>

$(function(){ 
    $('.demo').dad();
});

滚动页面触发CSS 动画效果插件 (WOW.js)

(ps:这肯定是一位魔兽玩家写的插件,s(【pic】乛◡乛【pic】))

如标题所见,这是用在滑动页面时候用来触发css动画的插件,像很多官网上滚动鼠标到一定距离之后就出发了元素上的渐入动画之类的;所以一般这个插件是需要搭配animated.css 使用的,使用起来也很方便
一个简单例子:

<link rel="stylesheet" type="text/css" href="../js/animate.min.css">
<script data-original="../js/wow.min.js"></script>

<div style="display:flex;justify-content:space-between;width:1200px;margin:50px auto">
    <div class="wow fadeInLeft testblock" data-wow-duration="1.5s" data-wow-delay="0.3"></div>
    <div class="wow fadeInUp testblock" data-wow-duration="1.5s" data-wow-delay="0.3"></div>
    <div class="wow fadeInDown testblock" data-wow-duration="1.5s" data-wow-delay="0.3"></div>
    <div class="wow fadeInRight testblock" data-wow-duration="1.5s" data-wow-delay="0.3"></div>
</div>

实例化:

var wow = new WOW({
    boxClass: 'wow', //
    animateClass: 'animated',
    offset: 0,
    mobile: true,
    live: true
});
wow.init();
属性/方法类型默认值说明
boxClassstringwowwow需要执行动画的元素的 class, 与标签上的class 对应
animateClassstringanimatedanimated.css 提供的指定动画class
offsetnum0距离可视区域多少开始执行动画
mobilebooltrue移动设备上是否执行动画
livebooltrue异步加载进来的元素是否应用动画

其实配置的话一般来说就这样就可以的了,你只需要在你想要的使用动画的元素上加上指定class就好了,其中 'wow' 代表这个元素需要使用wow记录位置使用wow插件功能,然后fadeInUp 这些就是animated.css 提供的预置效果,想要使用那些效果直接替换class就好,data-wow-duration是指动画持续时间,可以自由调整,data-wow-delay是指动画延迟多长时间执行

查看原文

赞 1 收藏 1 评论 0

墨韵 发布了文章 · 1月9日

promise 初步使用

Promise 是异步编程的一种解决方案,比传统的解决方案–回调函数和事件更加高效,它开始是在node社区提出和实现,后面ES6将其写进了语言标准,统一了语法,原生提供了Promise;
promise听着很陌生,其实我们一直都在使用
那么promise是什么呐?首先字面意思翻译就知道,保证,承诺的意思;也就是说时间是未来;好比说---我以后给你怎么怎么样---,接合上面的那句"比传统的解决方案–回调函数和事件更加高效"就知道个大概了;“承诺将来会执行”的对象(或者说方法,函数,函数本质也是个对象)在JavaScript中称为Promise对象。最典型的就是ajax;ajax就是承诺将会在请求结束后给你个结果,不管是成功或者失败;

js是一门单线程的语言,无法做到那些强语言的多线程操作,只因为js的宿主环境是浏览器,所以有时候看起来像是多线程;
由于这个“单线程缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现;比方在一段动画结束之后弹出个窗口

obj.animate({
    width:200
},300,function(){
    alert(1)
})

先说一下普通的回调函数的不足之处,如果只是像上面的那样的操作用这个回调操作也是很愉快的,但是又没想过,假设有一串任务队列呢,做完第一件事之后去做第二,第三...第N件事情呢;那么就会出现这样的现象

//三个简单的div,我想让第一个向右移动到600px处之后,第二个开始移动到600px,在第三个....
<div class="a1"></div>
<div class="a2"></div>
<div class="a3"></div>
//假设用传统的回调
    var disx=a1.offset().left;
        var timer=setInterval(function(){
            if(disx<=600){
                disx+=30;
                a1.css({
                    left:disx
                })
            }
            else{
                clearInterval(timer);
                var disx2=a2.offset().left;
                var timer2=setInterval(function(){
                    if(disx2<=600){
                        disx2+=30;
                        a2.css({
                            left:disx2
                        })
                    }
                    else{
                        clearInterval(timer2);
                        var disx3=a3.offset().left;
                        var timer3=setInterval(function(){
                            if(disx3<=600){
                                disx3+=30;
                                a3.css({
                                    left:disx3
                                })
                            }
                            else{
                                clearInterval(timer3);
                            }
                        }, 30)
                    }
                },30)
            }
        }, 30);

可以看到左侧已经空出来了一大块'三角形'了,假设n个呢...那就有些崩溃了;回调会越来越多,宛如地狱一般,所以这种现象被称之为"回调地狱",就像地狱一般无法自拔

那么promise可以干什么呢,他可以把事件操作和事件结果处理分开,我们可以不关心事件操作,直关心操作结果,成功我们就走成功,失败就走失败,我知道你现在心里一定在想jquery的ajax,没错,其实那就是封装好的一个promise;

那好,一个ajax

$.ajax({
    url: '/path/to/file',
    type: 'default GET (Other values: POST)',
    dataType: 'default: Intelligent Guess (Other values: xml, json, script, or html)',
    data: {param1: 'value1'},
})
.done(function(data) {
    console.log("success");//成功走这儿
})
.fail(function(err) {
console.log("error"); //失败走这儿
})
.always(function() {
console.log("complete"); //不管成功失败都会走这儿
});

从上面的就可以看出貌似和平时的有些不一样;我们对于结果而言只需要关注在后面.done(),.fail(),.always()

因为有ajax这个很熟悉的操作,所以我们会先入为主的认为这个promise就是拿来干ajax的事情的...

其实不然,别忘了最上面的第一句话"Promise 是异步编程的一种解决方案" js的异步可不单单是ajax,
1、定时器都是异步操作
2、事件绑定都是异步操作
3、AJAX中一般我们都采取异步操作(也可以同步)
4、回调函数可以理解为异步(不是严谨的异步操作)

然后来一个和定时器接合的promise的例子

    var p=new Promise(function(resolve,reject){
        console.log(1)
        setTimeout(function(){
            var n=parseInt(Math.random()*10);
            if(n>5){
                resolve(n)
            }
            else{
                reject(n)
            }
        },3000);
    })
    p.then(function(num){
        console.log('成功了,这个数是 ', num)
    }).catch(function(num){
        console.log('失败了,这个数是 ',num)
    });

结果: 1 ,然后3s之后得到成功了,这个数是, 6

从这个东西我们至少可以得到几个关键信息
1,promise不单单是用在ajax上的,所有异步操作我们都可以用;
2,promise接受一个函数resolver作为参数,而这个函数又接受2个函数作为参数,这两个函数分别对应成功和失败的操作逻辑
3,成功和失败由我们自己来定义 ajax有一个既定事实,东西没搞回来我们一般认为他失败了,搞回来了我们则认为他成功了,如果从前面的那'成功和失败由我们自己来定义 '来说,我们也可以说东西没回来是成功的,回来了则是失败,只是没人这么干而已;
4,不管这么样,他内部的操作总是会跑一次,也就是console.log(1);所以看起来貌似没调用函数但是也跑了一次
5,注入的两个参数resolve,reject在外面一定有对应的操作等着

注意到那个then,catch了嘛?这就是promise的另一半,操作完成后我们需要进行的操作;这不用讲大家都明白;
就是这东西的写法挺多的,比如

var p=new Promise(function(resolve,reject){
    //.......
});
p.then(function(){
    //...
},function(){
    //...这样就没了catch();
})

在用jquery的ajax说,一般常用的

$.ajax({
    url:'xx',
    success:function(data){
        //ok
    },
    error:function(err){
        //err
    }
})

上面你看到了那东西还有done,fail...
比较贴近promise的还可以这么干

$.ajax({//...}).then().catch()

其实多写一点儿就可以看到这个东西就是个promise对象,

var p=$.ajax({//...});
log(p);//Promise {<pending>},你可以展开Promise看多更多内部东西
p.then().catch()

好,又出现了新的单词
pending(在…期间,直到…时为止;);处于事件进行中,等待结果中;
resolve(决心要做…);出好结果了,我应该做正确的事;
reject(拒绝);出坏结果了,我应该做坏结果对应的事;
这哥仨一下就被联想在了一起,
没错,这正是promise的三种状态,等待,成功,失败;其中成功失败是由等待转变,这一过程不可逆;

有时候经常会遇到连续依赖调用,先请求第一个接口,然后接收到返回的一些数据,然后利用得到的数据,在去请求第二个接口,然后又接收传回来的数据,然后在去请求第三个接口....你可能会得到这样结构

    $.ajax({
        url:'example/1',
        type:'GET',
        data:xx,
        ....
        success:function(data){
            $.ajax({
                url:data.url,
                type:'GET',
                data:xx,
                ....
                success:function(data){
                    $.ajax({
                        url:'example/2',
                        type:'GET',
                        data:{'name':data.name}
                        ....
                        success:function(data){
                            console.log(data)
                        }
                    })
                }
            })
        }
    })

当然,你可能会有好一些的办法,现在promise可以这样做,假设我有一个url.json文件里面存有一个接口,我先去得到这个json文件,得到之后在根据它里面的内容再去请求...

    function getdata(url){
        var p=new Promise(function(resolve,reject){
            $.ajax({
                url: url,
                type:"GET",
                datatype:"jsonp",
                success: function (d) {
                   resolve(d)
                },
                error:function(err){
                    reject(err)
                }
            });
        })
        return p;
    }

    var p1=getdata('url.json');
    p1.then(function(data){
        getdata(data.url)
    }).then(function(data){
        console.log(data)
    }).catch(function(err){
        console.log(err)
    })

;
这是一个今日头条的热词接口,先不管下面的报错,那个是跨域造成的,反正就结果而言,东西是拿回来了也可以看到确实发出了2个请求,但是在写法上好了很多;

promise的链式写法和jquery很像,jquery之所以可以不停点下去,也在于他每一次操作之后都会返回一个dom对象,这个promise也一样,每一次都会返回一个promise对象,构成了链式调用...

除了这种链式调用场景,还可能会遇到一次性要发出多个请求,emm 也就是并行异步任务了,普通方法就随便怎么自由实现了,看一下promise的做法吧

    function getText(url){
        var p=new Promise(function(resolve,reject){
            $.ajax({
                url:url,
                type:'get',
                datatype:'html',
                success:function(data){
                    resolve(data)
                },
                error:function(err){
                    reject(err)
                }
            })
        });
        return p;
    }
    var get1=getText('../c.html');
    var get2=getText('../b.html');
    Promise.all([get1,get2]).then(function(data){
        console.log(data)
    })


同时执行ge1t和get2,并在它们都完成后执行then,最后结果被装载了一个数组,分别对应各自的请求;

Promise 的用法确实变化太多,因为只是初尝,所以很多地方还是懵逼状态,只留下了最基本的用法

补充:之前一直对resolve存在不少疑惑,包括现在也是,resolve和reject只是两个状态切换器,可以这么说,promise里面的代码都是处于pending状态,是正在执行中的;而代码一旦遇到resolve或者reject就说明到了有了结果了,你可以在你任何想要让pending代码状态转换的时候使用两个抑制力,而且resolve/reject其实不一定要带上参数,比方你只需要明确这里是标志有结果了,我应该执行下一步了,那么你就可以使用抑制力了

比方一个例子,点击某个按钮,让处于屏幕外的元素依次运动到屏幕里面来

//这几个按钮一开始是处于屏幕外面的
<div class="kechenzhixun-btn kechenzhixun-btn-a" >课程咨询</div>
    <div class="kechen-zhixun">
        <div class="zhixun-bnt-group">
            <div style="color:white;font-size:14px;margin-bottom:18px">选择对应课程</div>
            <div class="kechenzhixun-btn kechenzhixun-btn-b kechenzhixun-btn-b-1">产品课程</div>
            <div class="kechenzhixun-btn kechenzhixun-btn-b kechenzhixun-btn-b-2">运营课程</div>
            <div class="kechenzhixun-btn kechenzhixun-btn-b kechenzhixun-btn-b-3">专项技能班</div>
            <div class="kechenzhixun-btn kechenzhixun-btn-b kechenzhixun-btn-b-4 close-kechen-zhixun"> 关闭</div>
        </div>
    </div>

我们使用promise来实现这个功能

function move(obj){
    return new Promise(function(resolve,reject){
        var tagObj=document.querySelector('.'+obj);
        var timer;
        if(parseInt(tagObj.offsetLeft)>0){
            timer=setInterval(function(){
                tagObj.style.left=tagObj.offsetLeft-50+'px';
                if(parseInt(tagObj.offsetLeft)<0){
                    clearInterval(timer);
                    resolve();
                }
            },41)
        }else{
            timer1=setInterval(function(){
                tagObj.style.left=tagObj.offsetLeft+30+'px';
                if(parseInt(tagObj.offsetLeft)>140){
                    clearInterval(timer1);
                    resolve();
                }
            },30)
        }
    })
}

上面就是一个简单的例子,点击依次出现,再点击依次退出去,在这里的两个resolve里面就并没有带参数,这里仅仅用作标识,代表状态转换,从pending转换到了resolve;
对于需要在里面带上参数的情况我想大部分应该存在在数据请求上

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 8 次点赞
  • 获得 4 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 4 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-09-04
个人主页被 208 人浏览