js插件编写(如何用原生js写一个弹窗消息提醒插件)

本文目录
- 如何用原生js写一个弹窗消息提醒插件
- 如何用前端js模块化编写require.js
- 如何写vue插件vue.js实例教学
- 如何将JS插件改写成Vue的插件
- 如何编写一个js/jQuery库(心得总结)
- js怎么封装Canvas成插件
- js如何写插件教程分享
- 怎样用原生JS封装自己需要的插件
- 使用JS如何实现瀑布流插件
如何用原生js写一个弹窗消息提醒插件
1.分析
当消息被触发的时候,会有一个自上而下的淡入过程。
在持续了一段时间后会自动的消失,或者是需要用户来手动的点击关闭按钮。
在消息消失的时候,会有一个自下而上的淡出过程。
消息是可以叠加弹出的,最新的消息会排在消息列表的最后面。
当前面的消息消失后,后面的消息会有一个向上滑动效果。
然后消息本身是有三部分组成
消息图标,用来区分不同类型的消息。
消息文本。
关闭按钮,并不是所有消息都需要关闭按钮。
2. 实现样式
那么,不管我们是用原生js还是vue,首先呢,我们都需要把这个消息的基本样式给写出来,然后再通过js来控制消息的弹出和关闭。
所以,我们先来写html和css。
《!-- message.html --》
***隐藏网址***
***隐藏网址***
《link rel="stylesheet" href="./message.css" rel="external nofollow" 》
《script src="./message.js"》《/script》
《!-- 消息外层容器,因为消息提醒基本上是全局的,所以这里用id,所有的弹出消息都是需要插入到这个容器里边的 --》
《div id="message-container"》
《div class="message"》
《!-- 消息图标 icon icon-success对应我的阿里字体图标的font-class --》
《div class="type icon icon-success"》《/div》
《!-- 消息文本 --》
《div class="text"》这是一条正经的消息~《/div》
《!-- 关闭按钮 --》
《div class="close icon icon-close"》《/div》
《/div》
《div class="message"》
《div class="type icon icon-error"》《/div》
《div class="text"》这是一条正经的消息~《/div》
《/div》
《/div》
/* message.css */
#message-container {
position: fixed;
left: 0;
top: 0;
right: 0;
/* 采用flex弹性布局,让容器内部的所有消息可以水平居中,还能任意的调整宽度 */
display: flex;
flex-direction: column;
align-items: center;
}
#message-container .message {
background: #fff;
margin: 10px 0;
padding: 0 10px;
height: 40px;
box-shadow: 0 0 10px 0 #eee;
font-size: 14px;
border-radius: 3px;
/* 让消息内部的三个元素(图标、文本、关闭按钮)可以垂直水平居中 */
display: flex;
align-items: center;
}
#message-container .message .text {
color: #333;
padding: 0 20px 0 5px;
}
#message-container .message .close {
cursor: pointer;
color: #999;
}
/* 给每个图标都加上不同的颜色,用来区分不同类型的消息 */
#message-container .message .icon-info {
color: #0482f8;
}
#message-container .message .icon-error {
color: #f83504;
}
#message-container .message .icon-success {
color: #06a35a;
}
#message-container .message .icon-warning {
color: #ceca07;
}
#message-container .message .icon-loading {
color: #0482f8;
}
3. 实现动画
接下来要做的就是这个消息的弹出和消失动画,我们还是用css来实现。
想要在css里边实现自定义的动画,首先需要用@keyframes来定义一个动画规则,然后再通过animation属性把动画应用到某个元素上就可以了。
所谓的动画规则其实就是一个动画序列,或者可以理解为一个个的关键帧,而关键帧的内部就是你想改变的css属性,你可以在关键帧里边写上几乎任何的css属性,当动画被应用的时候,这些css属性就会根据各个关键帧做出相应的变换。
那我们先用@keyframes来写一个动画规则吧
/* message.css */
/* 这个动画规则我们就叫做message-move-in吧,随后我们会用animation属性在某个元素上应用这个动画规则。 */
@keyframes message-move-in {
0% {
/* 前边分析过了,弹出动画是一个自上而下的淡入过程 */
/* 所以在动画初始状态要把元素的不透明度设置为0,在动画结束的时候再把不透明度设置1,这样就会实现一个淡入动画 */
opacity: 0;
/* 那么“自上而下”这个动画可以用“transform”变换属性结合他的“translateY”上下平移函数来完成 */
/* translateY(-100%)表示动画初始状态,元素在实际位置上面“自身一个高度”的位置。 */
transform: translateY(-100%);
}
100% {
opacity: 1;
/* 平移到自身位置 */
transform: translateY(0);
}
}
然后我们再定义一个和message元素同级的类move-in,把message-move-in这个动画规则给应用到move-in类上,这样我们需要让哪个消息弹出,就只需要在消息的类上加一个move-in就行。
/* message.css */
#message-container .message.move-in {
***隐藏网址***
animation: message-move-in 0.3s ease-in-out;
}
可以看到,只需要在某个message上追加一个move-in就能实现弹出动画。
那么,消失动画也是一个套路,只不过跟弹出动画反过来而已。
/* message.css */
@keyframes message-move-out {
0% {
opacity: 1;
transform: translateY(0);
}
100% {
opacity: 0;
transform: translateY(-100%);
}
}
#message-container .message.move-out {
animation: message-move-out 0.3s ease-in-out;
/* 让动画结束后保持结束状态 */
animation-fill-mode: forwards;
}
animation-fill-mode: forwards;这个是干嘛的呢?因为动画结束后默认会回到元素的最初状态,在这里表现的是消失后又出现了。
所以animation-fill-mode: forwards;是为了让动画结束后保持这个结束状态,也就是不在显示了。
4. 编写js插件
那么,在写js之前呢,我们先来思考一下,如果你是插件的使用者,你想怎么来调用这个插件?
我们的插件很简单,就是在需要的时候弹出一个消息,假设插件他提供给我们的是一个类,就叫做Message吧,并且他内部有一个show方法,那么只要使用者实例化这个类后,调用他的show方法,然后传入不同的参数就可以弹出一个消息了。而且我们所实例化的对象可以是全局唯一的。
《!-- message.html --》
《!-- 省略... --》
《script》
// message可以定义为全局对象,项目中可以直接调用。
const message = new Message();
message.show({
type: ’success’,
text: ’点个关注不迷路~’
});
《/script》
所以呢,我们要先写一个Message类,并且必须要实现一个show方法。
/* message.js */
class Message {
constructor() {
}
show({ type = ’info’, text = ’’ }) {
}
}
这里我直接用了es6的class关键词,其实他的内部还是原型链的形式。用class呢,可以让我们更直观的了解这个类。
根据我们在第一部分的分析,所有的消息元素都是需要在js中创建的,所以我们不需要使用者来写任何html代码,那么我们只需要在对象被实例化new Message()的时候,就去创建消息容器message-container,后续在调用show方法时候,直接把消息插入到message-container内部即可。
/* message.js */
class Message {
/**
* 构造函数会在实例化的时候自动执行
*/
constructor() {
const containerId = ’message-container’;
// 检测下html中是否已经有这个message-container元素
this.containerEl = document.getElementById(containerId);
if (!this.containerEl) {
// 创建一个Element对象,也就是创建一个id为message-container的dom节点
this.containerEl = document.createElement(’div’);
this.containerEl.id = containerId;
// 把message-container元素放在html的body末尾
document.body.appendChild(this.containerEl);
}
}
show({ type = ’info’, text = ’’ }) {
}
}
这样,我们调用const message = new Message()的时候会在dom中自动的插入一个message-container节点。
那么,最重要的还是我们的show方法:
创建一个消息节点,并把它追加到message-container容器的末尾。
设定一个时间,在这个时间结束后自动的将消息移除。
监听“关闭按钮”的click事件,来让用户可以手动的移除消息。
我们一步一步来。
4.1 创建一个消息节点,并把它追加到message-container容器的末尾。
class Message {
// 省略...
show({ type = ’info’, text = ’’ }) {
// 创建一个Element对象
let messageEl = document.createElement(’div’);
// 设置消息class,这里加上move-in可以直接看到弹出效果
messageEl.className = ’message move-in’;
// 消息内部html字符串
messageEl.innerHTML = `
《span class="icon icon-${type}"》《/span》
《div class="text"》${text}《/div》
《div class="close icon icon-close"》《/div》
`;
// 追加到message-container末尾
// this.containerEl属性是我们在构造函数中创建的message-container容器
this.containerEl.appendChild(messageEl);
}
我们来调用下试试~
《!-- message.html --》
《!-- 省略... --》
《button class="btn"》弹窗消息提醒《/button》
《script》
// message可以定义为全局对象,项目中可以直接调用。
const message = new Message();
document.querySelector(’.btn’).addEventListener(’click’, () =》 {
message.show({
type: ’success’,
text: ’点个关注不迷路~’
});
});
《/script》
4.2 设定一个时间,在这个时间结束后自动的将消息移除。
// message.js
class Message {
// 省略...
show({ type = ’info’, text = ’’, duration = 2000 }) {
// 省略...
// 用setTimeout来做一个定时器
setTimeout(() =》 {
// Element对象内部有一个remove方法,调用之后可以将该元素从dom树种移除!
messageEl.remove();
}, duration);
}
}
可以看到,消息在过了2秒后,自动的从dom树中移除了,不过呢并没有动画,还记得前边我们写了move-out类吗?这个类和message是同级的。现在我们只需要在定时结束后把这个类应用到message元素上就行。
// message.js
class Message {
// 省略...
show({ type = ’info’, text = ’’, duration = 2000 }) {
// 省略...
// 用setTimeout来做一个定时器
setTimeout(() =》 { // 首先把move-in这个弹出动画类给移除掉,要不然会有问题,可以自己测试下
messageEl.className = messageEl.className.replace(’move-in’, ’’); // 增加一个move-out类
messageEl.className += ’move-out’;
// 这个地方是监听动画结束事件,在动画结束后把消息从dom树中移除。
// 如果你是在增加move-out后直接调用messageEl.remove,那么你不会看到任何动画效果
messageEl.addEventListener(’animationend’, () =》 {
// Element对象内部有一个remove方法,调用之后可以将该元素从dom树种移除!
messageEl.remove();
});
}, duration);
}
}
4.3 监听“关闭按钮”的click事件,来让用户可以手动的移除消息。
有时候呢,我们希望消息能够一直展示,直到用户来手动的关闭掉,那么首先我们要加一个参数,用来控制是否展示这个关闭按钮。
// message.js
class Message {
// 省略...
show({ type = ’info’, text = ’’, duration = 2000, closeable = false }) {
// 创建一个Element对象
let messageEl = document.createElement(’div’);
// 设置消息class,这里加上move-in可以直接看到弹出效果
messageEl.className = ’message move-in’;
// 消息内部html字符串
messageEl.innerHTML = `
《span class="icon icon-${type}"》《/span》
《div class="text"》${text}《/div》
`;
// 是否展示关闭按钮
if (closeable) {
// 创建一个关闭按钮
let closeEl = document.createElement(’div’);
closeEl.className = ’close icon icon-close’;
// 把关闭按钮追加到message元素末尾
messageEl.appendChild(closeEl);
// 监听关闭按钮的click事件,触发后将调用我们的close方法
// 我们把刚才写的移除消息封装为一个close方法
closeEl.addEventListener(’click’, () =》 {
this.close(messageEl)
});
}
// 追加到message-container末尾
// this.containerEl属性是我们在构造函数中创建的message-container容器
this.containerEl.appendChild(messageEl);
// 只有当duration大于0的时候才设置定时器,这样我们的消息就会一直显示
if (duration 》 0) {
// 用setTimeout来做一个定时器
setTimeout(() =》 {
this.close(messageEl);
}, duration);
}
}
/**
* 关闭某个消息
* 由于定时器里边要移除消息,然后用户手动关闭事件也要移除消息,所以我们直接把移除消息提取出来封装成一个方法
* @param {Element} messageEl
*/
close(messageEl) {
// 首先把move-in这个弹出动画类给移除掉,要不然会有问题,可以自己测试下
messageEl.className = messageEl.className.replace(’move-in’, ’’);
// 增加一个move-out类
messageEl.className += ’move-out’;
// 这个地方是监听动画结束事件,在动画结束后把消息从dom树中移除。
// 如果你是在增加move-out后直接调用messageEl.remove,那么你不会看到任何动画效果
messageEl.addEventListener(’animationend’, () =》 {
// Element对象内部有一个remove方法,调用之后可以将该元素从dom树种移除!
messageEl.remove();
});
}
}
我们来调用下试试~
《!-- message.html --》
《!-- 省略... --》
《button class="btn"》弹窗消息提醒《/button》
《script》
/
如何用前端js模块化编写require.js
requirejs是一个JavaScript文件和模块加载器。requireJS允许你把你的javascript代码独立成文件和模块,同时管理每个模块间的依赖关系。
一、为什么要用require.js?
最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了。后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载。下面的网页代码,相信很多人都见过:
《script src="1.js"》《/script》
《script src="2.js"》《/script》
《script src="3.js"》《/script》
《script src="4.js"》《/script》
《script src="5.js"》《/script》
《script src="6.js"》《/script》这段代码依次加载多个js文件。
这样的写法有很大的缺点。
首先,加载的时候,浏览器会停止网页渲染,加载文件越多,网页失去响应的时间就会越长
由于js文件之间存在依赖关系,因此必须严格保证加载顺序(比如上例的1.js要在2.js的前面),依赖性最大的模块一定要放到最后加载,当依赖关系很复杂的时候,代码的编写和维护都会变得困难。
require.js的诞生,就是为了解决这两个问题:
实现js文件的异步加载,避免网页失去响应;
管理模块之间的依赖性,便于代码的编写和维护。
二、require.js的加载
使用require.js的第一步,是先去官方网站下载最新版本。
下载后,假定把它放在js子目录下面,就可以加载了。
《script src="js/require.js"》《/script》
有人可能会想到,加载这个文件,也可能造成网页失去响应。解决办法有两个:
async属性表明这个文件需要异步加载,避免网页失去响应。IE不支持这个属性,只支持defer,所以把defer也写上。
data-main属性的作用是,指定网页程序的主模块。
在上例中,就是js目录下面的main.js,这个文件会第一个被require.js加载。由于require.js默认的文件后缀名是js,所以可以把main.js简写成main。
加载require.js以后,下一步就要加载我们自己的代码了。假定我们自己的代码文件是main.js,也放在js目录下面。那么,只需要写成下面这样就行了:
《script src="js/require.js" data-main="js/main"》《/script》一个是把它放在网页底部加载,另一个是写成下面这样:
《script src="js/require.js" defer async="true" 》《/script》三、主模块的写法
上一节的main.js,我把它称为"主模块",意思是整个网页的入口代码。它有点像C语言的main()函数,所有代码都从这儿开始运行。
下面就来看,怎么写main.js。
如果我们的代码不依赖任何其他模块,那么可以直接写入javascript代码。
// main.js
alert("加载成功!");但这样的话,就没必要使用require.js了。真正常见的情况是,主模块依赖于其他模块,这时就要使用AMD规范定义的的require()函数。
// main.js
require(, function (moduleA, moduleB, moduleC){
// some code here
});》 require()函数接受两个参数。第一个参数:
- 一个数组,表示所依赖的模块,上例就是,即主模块依赖这三个模块;
- 一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。
require()异步加载moduleA,moduleB和moduleC,浏览器不会失去响应;它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。
下面,我们看一个实际的例子。
假定主模块依赖jquery、underscore和backbone这三个模块,main.js就可以这样写:
require(, function ($, _, Backbone){
// some code here
});require.js会先加载jQuery、underscore和backbone,然后再运行回调函数。主模块的代码就写在回调函数中。
四、模块的加载
上一节最后的示例中,主模块的依赖模块是。默认情况下,require.js假定这三个模块与main.js在同一个目录,文件名分别为jquery.js,underscore.js和backbone.js,然后自动加载。
使用require.config()方法,我们可以对模块的加载行为进行自定义。require.config()就写在主模块(main.js)的头部。参数就是一个对象,这个对象的paths属性指定各个模块的加载路径。
require.config({
paths: {
"jquery": "jquery.min",
"underscore": "underscore.min",
"backbone": "backbone.min"
}
});上面的代码给出了三个模块的文件名,路径默认与main.js在同一个目录(js子目录)。如果这些模块在其他目录,比如js/lib目录,则有两种写法。
一种是逐一指定路径。
require.config({
baseUrl: "js/lib",
paths: {"jquery": "jquery.min", "underscore": "underscore.min", "backbone": "backbone.min"}
});另一种则是直接改变基目录(baseUrl)。
require.config({ baseUrl: "js/lib",
paths: { "jquery": "jquery.min", "underscore": "underscore.min", "backbone": "backbone.min"
}
});如果某个模块在另一台主机上,也可以直接指定它的网址,比如:
require.config({
paths: {
***隐藏网址***
}
});require.js要求:
每个模块是一个单独的js文件。这样的话,如果加载多个模块,就会发出多次HTTP请求,会影响网页的加载速度。因此,require.js提供了一个优化工具,当模块部署完毕以后,可以用这个工具将多个模块合并在一个文件中,减少HTTP请求数。
五、AMD模块的写法
require.js加载的模块,采用AMD规范。也就是说,模块必须按照AMD的规定来写。
具体来说,就是模块必须采用特定的define()函数来定义。如果一个模块不依赖其他模块,那么可以直接定义在define()函数之中。
假定现在有一个math.js文件,它定义了一个math模块。那么,math.js就要这样写:
// math.js
define(function (){
var add = function (x,y){
return x+y;
};
return {
add: add
};
});加载方法如下:
// main.js
require(, function (math){
alert(math.add(1,1));
});如果这个模块还依赖其他模块,那么define()函数的第一个参数,必须是一个数组,指明该模块的依赖性。
define(, function(myLib){
function foo(){
myLib.doSomething();
}
return {
foo : foo
};
});当require()函数加载上面这个模块的时候,就会先加载myLib.js文件。
六、加载非规范的模块
理论上,require.js加载的模块,必须是按照AMD规范、用define()函数定义的模块。但是实际上,虽然已经有一部分流行的函数库(比如jQuery)符合AMD规范,更多的库并不符合。那么,require.js是否能够加载非规范的模块呢?
回答是可以的。
这样的模块在用require()加载之前,要先用require.config()方法,定义它们的一些特征。
举例来说,underscore和backbone这两个库,都没有采用AMD规范编写。如果要加载它们的话,必须先定义它们的特征。
require.config({
shim: { ’underscore’: { exports: ’_’ }, ’backbone’: {
deps: , exports: ’Backbone’
}
}
});require.config()接受一个配置对象,这个对象除了有前面说过的paths属性之外,还有一个shim属性,专门用来配置不兼容的模块。具体来说,每个模块要定义(1)exports值(输出的变量名),表明这个模块外部调用时的名称;(2)deps数组,表明该模块的依赖性。
比如,jQuery的插件可以这样定义:
shim: {
’jquery.scroll’: {
deps: ,
exports: ’jQuery.fn.scroll’
}
}七、require.js插件
require.js还提供一系列插件,实现一些特定的功能:
domready插件,可以让回调函数在页面DOM结构加载完成后再运行。
require(, function (doc) { // called once the DOM is ready
});text和image插件,则是允许require.js加载文本和图片文件。
define(,
function (review, cat) { console.log(review); document.body.appendChild(cat); } );类似的插件还有json和mdown,用于加载json文件和markdown文件
本篇介绍了用前端js模块化编写require.js,想要更多相关学习内容请关注Gxl网。
如何写vue插件vue.js实例教学
本文主要和大家分享如何写vue插件,在学习之前,先问问自己,为什么要编写vue的插件。
在一个项目中,尤其是大型项目,有很多部分需要复用,比如加载的loading动画,弹出框。如果一个一个的引用也稍显麻烦,而且在一个vue文件中引用的组件多了,会显得代码臃肿,所以才有了封装vue插件的需求。
说完需求,就来看看具体实现。目前我尝试了两种不一样的插件编写的方法,逐个介绍。
这是我的项目目录,大致的结构解释这样,尽量简单,容易理解。
一个是loading插件,一个是toast插件,不同的地方在于:loading插件是作为组件引入使用,而toast插件是直接添加在挂载点里,通过方法改变状态调用的。
目前使用起来是酱紫的:
toast插件
toast文件下有两个文件,后缀为vue的文件就是这个插件的骨架,js文件一个是将这个骨架放入Vue全局中,并写明操作逻辑。
可以看一下toast.vue的内容:
《template》
《transition name="fade"》
《p v-show="show"》
{{message}}
《/p》
《/transition》
《/template》
《script》
export default {
data() {
return {
show: false,
message: ""
};
}
};
《/script》
《style lang="scss" scoped》
.toast {
position: fixed;
top: 40%;
left: 50%;
margin-left: -15vw;
padding: 2vw;
width: 30vw;
font-size: 4vw;
color: #fff;
text-align: center;
background-color: rgba(0, 0, 0, 0.8);
border-radius: 5vw;
z-index: 999;
}
.fade-enter-active,
.fade-leave-active {
transition: 0.3s ease-out;
}
.fade-enter {
opacity: 0;
transform: scale(1.2);
}
.fade-leave-to {
opacity: 0;
transform: scale(0.8);
}
《/style》
这里面主要的内容只有两个,决定是否显示的show和显示什么内容的message。
粗看这里,有没有发现什么问题?
这个文件中并没有props属性,也就是无论是show也好,message也好,就没有办法通过父子组件通信的方式进行修改,那他们是怎么正确处理的呢。别急,来看他的配置文件。
index.js:
import ToastComponent from ’./toast.vue’
const Toast = {};
// 注册Toast
Toast.install = function (Vue) {
// 生成一个Vue的子类
// 同时这个子类也就是组件
const ToastConstructor = Vue.extend(ToastComponent)
// 生成一个该子类的实例
const instance = new ToastConstructor();
// 将这个实例挂载在我创建的p上
// 并将此p加入全局挂载点内部
instance.$mount(document.createElement(’p’))
document.body.appendChild(instance.$el)
// 通过Vue的原型注册一个方法
// 让所有实例共享这个方法
Vue.prototype.$toast = (msg, duration = 2000) =》 {
instance.message = msg;
instance.show = true;
setTimeout(() =》 {
instance.show = false;
}, duration);
}
}
export default Toast
这里的逻辑大致可以分成这么几步:
创建一个空对象,这个对象就是日后要使用到的插件的名字。此外,这个对象中要有一个install的函数。使用vue的extend方法创建一个插件的构造函数(可以看做创建了一个vue的子类),实例化该子类,之后的所有操作都可以通过这个子类完成。之后再Vue的原型上添加一个共用的方法。
这里需要着重提的是Vue.extend()。举个例子,我们日常使用vue编写组件是这个样子的:
Vue.component(’MyComponent’,{
template:’《p》这是组件《/p》’
})
这是全局组件的注册方法,但其实这是一个语法糖,真正的运行过程是这样的:
let component = Vue.extend({
template:’《p》这是组件《/p》’
})
Vue.component(’MyComponent’,component)
Vue.extend会返回一个对象,按照大多数资料上提及的,也可以说是返回一个Vue的子类,既然是子类,就没有办法直接通过他使用Vue原型上的方法,所以需要new一个实例出来使用。
在代码里console.log(instance)
得出的是这样的结果:
可以看到$el:p.toast
也就是toast组件模板的根节点。
疑惑的是,我不知道为什么要创建一个空的p节点,并把这个实例挂载在上面。我尝试注释这段代码,但是运行会报错。
查找这个错误的原因,貌似是因为
document.body.appendChild(instance.$el)
这里面的instance.$el的问题,那好,我们console下这个看看。WTF!!!!结果居然是undefined。
那接着
console.log(instance)
和上一张图片比对一下,发现了什么?对,$el消失了,换句话说在我注释了
instance.$mount(document.createElement(’p’))
这句话之后,挂载点也不存在了。接着我试着改了一下这句:
instance.$mount(instance.$el)
$el又神奇的回来了??????
暂时没有发现这种改动有什么问题,可以和上面一样运行。但无论如何,这也就是说instance实例必须挂载在一个节点上才能进行后续操作。
之后的代码就简单了,无非是在Vue的原型上添加一个改变插件状态的方法。之后导出这个对象。
接下来就是怎么使用的问题了。来看看main.js是怎么写的:
import Vue from ’vue’
import App from ’./App’
// import router from ’./router’
import Toast from ’./components/taost’
Vue.use(Toast)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
// router,
render: h =》 h(App)
}).$mount(’#app’)
这样就可以在其他vue文件中直接使用了,像这样:
// app.vue
《template》
《p id="app"》
《loading duration=’2s’ :isshow=’show’》《/loading》
《!-- 《button @click="show = !show"》显示/隐藏loading《/button》 --》
《button @click="toast"》显示taost弹出框《/button》
《/p》
《/template》
《script》
export default {
name: "app",
data() {
return {
show: false
};
},
methods: {
toast() {
this.$toast("你好");
}
}
};
《/script》
《style》
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
《/style》
通过在methods中增加一个方法控制写在Vue原型上的$toast对toast组件进行操作。
这样toast组件的编写过程就结束了,可以看到一开始gif图里的效果。
loading插件
经过上一个插件的讲解,这一部分就不会那么细致了,毕竟大多数都没有什么不同,我只指出不一样的地方。
《template》
《p class=’wrapper’ v-if="isshow"》
《p class=’loading’》
《img src="./loading.gif"》
《/p》
《/p》
《/template》
《script》
export default {
props: {
duration: {
type: String,
default: "1s" //默认1s
},
isshow: {
type: Boolean,
default: false
}
},
data: function() {
return {};
}
};
《/script》
《style lang="scss" scoped》
《/style》
这个就只是一个模板,传入两个父组件的数据控制显示效果。
那再来看一下该插件的配置文件:
import LoadingComponent from ’./loading.vue’
let Loading = {};
Loading.install = (Vue) =》 {
Vue.component(’loading’, LoadingComponent)
}
export default Loading;
这个和taoat的插件相比,简单了很多,依然是一个空对象,里面有一个install方法,然后在全局注册了一个组件。
比较
那介绍了这两种不同的插件编写方法,貌似没有什么不一样啊,真的是这样么?
来看一下完整的main.js和app.vue这两个文件:
// main.js
import Vue from ’vue’
import App from ’./App’
// import router from ’./router’
import Toast from ’./components/taost’
import Loading from ’./components/loading’
Vue.use(Toast)
Vue.use(Loading)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
// router,
render: h =》 h(App)
}).$mount(’#app’)
// app.vue
《template》
《p id="app"》
《loading duration=’2s’ :isshow=’show’》《/loading》
《!-- 《button @click="show = !show"》显示/隐藏loading《/button》 --》
《button @click="toast"》显示taost弹出框《/button》
《/p》
《/template》
《script》
export default {
name: "app",
data() {
return {
show: false
};
},
methods: {
toast() {
this.$toast("你好");
}
}
};
《/script》
《style》
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
《/style》
可以看出来,loading是显示的写在app.vue模板里的,而toast并没有作为一个组件写入,仅仅是通过一个方法控制显示。
来看一下html结构和vue工具给出的结构:
看出来了么,toast插件没有在挂载点里面,而是独立存在的,也就是说当执行
vue.use(toast)
如何将JS插件改写成Vue的插件
本篇文章给大家分享的是关于如何将JS插件改写成Vue的插件,内容很不错,有需要的朋友可以参考一下,希望可以帮助到大家。
很多刚入坑vue的小伙伴,想用一些原来不用框架的JS插件,用在vue中,可是发现没有效果。
这里我先贴个例子。
下图是一个插件的效果图
首先插件源代码需要下载。
找到里面的index.html,找到里面的20行到87行,复制出来,找到你vue的项目,新建个文件夹,新建个js文件,内容如下
import wavePng from ’./wave.png’
export default {
install(Vue){
Vue.directive(’wave’, {
// 当指令绑定好之后,立即触发的方法
inserted: function(el){
start(el)
},
// 当指令的值变化后会触发update
update: function(el, binding, vnode){
if(binding.value){
start(el)
}else{
stop()
}
}
})
}
}然后把刚刚粘贴的插件代码粘在最下面,记得把插件原先有的闭包去掉。这个改装的思路,就是改成Vue的自定义指令形式。
怎么使用呢,首先要在main.js中
import wave from ’./components/wave.js’
Vue.use(wave)然后在你需要的元素中绑定指令,下面来个demo
《template》
《p》
《p class="wave" v-wave="wave"》《span》60%《/span》《/p》
《button @click="wave = true"》start《/button》
《button @click="wave = false"》stop《/button》
《/p》
《/template》
《script》
import wave from ’./a’
export default {
data(){
return{
wave: true
}
}
}
《/script》
《!-- Add "scoped" attribute to limit CSS to this component only --》
《style scoped》
.wave{width:200px;height:200px;overflow:hidden;border-radius:50%;background:rgba(255,203,103,.6);margin:100px auto;position:relative;text-align:center;display:table-cell;vertical-align:middle;}
.wave span{display:inline-block;color:#fff;font-size:20px;position:relative;z-index:2;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%)
}
.wave canvas{position:absolute;left:0;top:0;z-index:1;}
《/style》最终改造完成,希望能对刚入坑Vue的朋友有所帮助。
如何编写一个js/jQuery库(心得总结)
编写js/jQuery插件有一些约定俗成的套路,根据这些套路依葫芦画瓢,代码的结构上就不会出现太大的问题了,特别推荐这个叫 javascript-patterns 的项目,一些demo让我收获良多。
基本结构
普通的库
用最基本的匿名函数实现即可
(function(){
var root = this;
root.YOURLIB = function(){
FUNC1 : function(){},
FUNC2 : function(){}
}
}())也可以使用call而不是使用闭包,此时两种写法等效,undersocre.js用的是call写法
(function(){
var root = this;
root.YOURLIB = function(){
FUNC1 : function(){},
FUNC2 : function(){}
}
}.call(this))jQuery 插件
通过一下方法可以使的插件可以 跨CMD/AMD/Browser
(function (factory) {
if (typeof define === ’function’ && define.amd) {
// AMD
define(, factory);
} else if (typeof exports === ’object’) {
// CommonJS
factory(require(’jquery’));
} else {
// Browser globals
factory(jQuery);
}
}(function ($) {
$.fn.render = function() {}
$.render2 = function() {}
}))当然,如果不考虑Seajs,RequireJS的话,最方便的还是使用匿名函数,然后把window.jQuery当成参数传进去
内部组织
我们以 bootstrap-select v1.6.3,smooth-scroll这俩项目来进行分析,
初始化
一般的Library都会提供一套defaults配置文件,然后使用的时候和用户自定义的settings进行extend, smooth-scroll中的那种
settings = extend(defaults, options ||{})); 写法就相当赞,可以以一种十分简单的方式防止空指针异常。剩下的就是根据业务划分业务的funciton了,对于如何划分,只有多加练习了。
另外,在注释里像写上 private 和 public 来区分对外接口和对内接口是个不错的习惯。
i18n与配置管理
i18n是吧那些国际话的字符全都放到defautls,比较优雅的做法是defaults定义一个对象,这样国际化文件
便能和原有的库文件进行分离,具体参考 bootstrap-datapicker
THE END
js怎么封装Canvas成插件
这次给大家带来js怎么封装Canvas成插件,js封装Canvas成插件的注意事项有哪些,下面就是实战案例,一起来看一下。
之前就说过,我想写一个canvas画统计图的插件,现在写好了
先说下实现的功能吧:
??1.可以通过自定义X轴坐标属性和Y轴坐标属性按比例画出统计图
??2.可以选择画折现图还是柱形统计图,或者两者都实现
??3.可以自由定义折现颜色,坐标颜色,柱形图颜色 和canvas边框颜色,当然边框你也可以选择要或者不要
??4.可以选择是否实现柱形图和折现图的动画实现
实现过程
画坐标——画箭头——做X轴和Y轴的标注——画柱形图——画折现图
话不多说,上代码
(function(window,document){
var ChartDraws = function(options){
if(!(this instanceof ChartDraws))return new ChartDraws(options);
this.options = $.extend({
//报表所需的参数
"containerId" : "", //canvas所在容器id
"canvasWidth" : 400,
"canvasHeight" : 300,
"paddingLeft" : 20,
"paddingTop" : 20,
"columnChartData" :, //柱形图的数量和对应得名称以及百分比
"yChartData" :, //y轴的数量及名称
"axisColor" : "white", //坐标轴颜色
"columnChartColor" : "#EEE685", //柱形图颜色
"isNeedAnimation" : true, //是否需要动画
"isNeedLineChart" : true, //是否需要折线图
"isNeedColumnChart" : true, //是否需要柱形图
"lineChartColor" : "#90EE90", //折线图颜色,当isNeedLineChart=true时有效
"isNeedBorder" : false, //canvas是否需要外边框
"borderColor" : "white" //外边框颜色
},options);
if(this.options.canvasWidth《=500)
{
this.axisBorderWidth = 3;
this.fontSize = 8;
}
else if(this.options.canvasWidth《=800){
this.axisBorderWidth = 4;
this.fontSize = 12;
}
else{
this.axisBorderWidth = 5;
this.fontSize = 16;
}
var self = this;
_init();
function _init(){
var canvasDom = document.createElement("canvas");
canvasDom.id = self.options.containerId+"_"+"canvas";
canvasDom.width = self.options.canvasWidth;
canvasDom.height = self.options.canvasHeight;
if(self.options.isNeedBorder){
canvasDom.style.borderWidth = 1;
canvasDom.style.borderStyle = "solid";
canvasDom.style.borderColor = self.options.borderColor;
}
document.getElementById(self.options.containerId).appendChild(canvasDom);
self.context = document.getElementById(self.options.containerId+"_"+"canvas");
self.ctx = self.context.getContext("2d");
_drawAxis();
}
function _drawAxis(){
var XYData =transformAxis( );
self.ctx.strokeStyle=self.options.axisColor;
drawLine(self.ctx,XYData,self.axisBorderWidth);
//画三角箭头
//画y轴三角箭头
drawLine(self.ctx,transformAxis(),self.axisBorderWidth);
//画x轴三角箭头
drawLine(self.ctx,transformAxis(),self.axisBorderWidth);
_drawCoordinatePoints();
}
function _drawCoordinatePoints(){
self.reactAngleWidth = (1-2*0.04)*(self.options.canvasWidth-(2*self.options.paddingLeft))/(self.options.columnChartData.length*2-1);
self.lineDataList = ;
for(var i = 0;i《self.options.columnChartData.length;i++)
{
drawXText(self.ctx,2*self.options.columnChartData.Name);
self.lineDataList.push({
x:2*self.options.columnChartData.NO*self.reactAngleWidth+self.options.paddingLeft+0.04*(self.options.canvasWidth-(2*self.options.paddingLeft))+self.reactAngleWidth/2,
y:self.options.canvasHeight-(self.options.paddingTop+(self.options.canvasHeight-2*self.options.paddingTop)*self.options.columnChartData.PT)
})
}
//画Y轴title 画y轴虚线
self.reactAngleHeight = (self.options.canvasHeight-2*self.options.paddingTop)/(self.options.yChartData.length+1);
for(var j = 0;j《self.options.yChartData.length;j++)
{
drawYText(self.ctx,3*self.options.paddingLeft/4,self.options.paddingTop+self.reactAngleHeight*(j+1),self.options.yChartData.Name);
//画虚线
drawDottedLine(self.ctx,self.options.paddingLeft,self.options.paddingTop+self.reactAngleHeight*(j+1),self.options.canvasWidth-self.options.paddingLeft,self.options.paddingTop+self.reactAngleHeight*(j+1),self.options.canvasWidth-2*self.options.paddingLeft,10,self.axisBorderWidth/2);
}
_drawColumnChart();
}
function _drawColumnChart(){
//柱形图循环
var reactAngleTimer = 1;
function loopColumnChart()
{
var columnChartLooped = window.requestAnimationFrame(loopColumnChart);
if(reactAngleTimer《=100)
{
for(var k=0;k《self.options.columnChartData.length;k++)
{
self.ctx.fillStyle =self.options.columnChartColor;
drawRectangle(self.ctx,self.lineDataList.PT*reactAngleTimer/100);
}
reactAngleTimer++;
}
else
{
window.cancelAnimationFrame(columnChartLooped);
columnChartLooped = null;
reactAngleTimer = 1;
if(self.options.isNeedLineChart)
{
loopLineChart();
}
}
}
//折线图循环
var lineTimer = 0;
function loopLineChart()
{
var lineChartLooped = window.requestAnimationFrame(loopLineChart);
if(lineTimer《self.lineDataList.length-1)
{
self.ctx.lineWidth = 2*self.axisBorderWidth/3;
if(lineTimer == 0)
{
drawCircle(self.ctx,self.lineDataList.y);
}
drawCircle(self.ctx,self.lineDataList.y);
self.ctx.beginPath();
self.ctx.moveTo(self.lineDataList.y);
self.ctx.lineTo(self.lineDataList.y);
self.ctx.strokeStyle = self.options.lineChartColor;
self.ctx.lineWidth = 2*self.axisBorderWidth/3;
self.ctx.stroke();
lineTimer++;
}
else
{
window.cancelAnimationFrame(lineChartLooped);
lineChartLooped = null;
lineTimer = 0;
}
}
//画柱形图
function drawRectangle(context,x,y,width,height){
context.beginPath();
context.fillRect(x,y,width,height);
}
//画圆
function drawCircle(context,x,y){
context.beginPath();
context.arc(x,y,self.axisBorderWidth/2,0,2*Math.PI,true);
context.strokeStyle=self.options.lineChartColor;
context.stroke();
context.closePath();
}
if(self.options.isNeedAnimation)
{
if(self.options.isNeedColumnChart)
{
loopColumnChart();
}
else
{
if(self.options.isNeedLineChart) {
loopLineChart();
}
}
}
else
{
if(self.options.isNeedColumnChart)
{
for(var k=0;k《self.options.columnChartData.length;k++)
{
self.ctx.fillStyle =self.options.columnChartColor;
drawRectangle(self.ctx,self.lineDataList.PT);
}
}
if(self.options.isNeedLineChart) {
for (var l = 0; l 《 self.lineDataList.length - 1; l++) {
self.ctx.lineWidth = 4;
if (l == 0) {
drawCircle(self.ctx, self.lineDataList.y);
}
drawCircle(self.ctx, self.lineDataList.y);
self.ctx.beginPath();
self.ctx.moveTo(self.lineDataList.y);
self.ctx.lineTo(self.lineDataList.y);
self.ctx.strokeStyle = self.options.lineChartColor;
self.ctx.lineWidth = 2*self.axisBorderWidth/3;
self.ctx.stroke();
}
}
}
}
function transformAxis(data)
{
var newData=;
for(var i=0;i《data.length;i++){
newData.push({
x:data.x,
y:self.options.canvasHeight-data.y
})
}
return newData;
}
function drawLine(context,point,width){
context.beginPath();
context.moveTo(point.y);
if(point.length》2)
{
for(var i=1;i《point.length;i++)
{
context.lineTo(point.y);
}
}
context.lineWidth = width;
context.lineJoin=’round’;
context.stroke();
context.closePath();
}
//画y轴title
function drawYText(context,x,y,str) {
context.beginPath();
context.font = ’{fontSize} Microsoft Yahei’.replace("{fontSize}",self.fontSize+"px");
context.fillStyle = ’white’;
context.textAlign = ’right’;
context.fillText(str,x,self.options.canvasHeight-y);
context.closePath();
}
//画x轴title
funct
js如何写插件教程分享
本文主要和大家分享js如何写插件教程分享,希望能帮助到大家。
《!doctype html》
《html lang="en"》
《head》
《meta charset="UTF-8" /》
《title》Document《/title》
《style type="text/css"》
#demo-1, #demo-2 {
width: 200px;
height: 200px;
border: 1px solid #ddd;
}
《/style》
《/head》
《body》
《h3》点击add可以添加个自input的内容到p里并实现变颜色《/h3》
《!--组件实例1--》
《p id="demo-1"》
《input type="" name="" id="" value="好的" /》
《button id="add-1"》add《/button》
《/p》
《br /》 《br /》
《!--组件实例2--》
《p id="demo-2"》
《input type="" name="" id="" value="11" /》
《button id="add-2"》add《/button》
《/p》
《script type="text/javascript"》
//这里是插件的代码;我为了方便都写到一个html中了;请把这个script标签中的内容单独写在一个js文件里
//整个插件写在一个立即执行函数里;就是function(){}();函数自执行;保证里面的变量不会与外界互相影响
//头部的win啊,doc啊 $ 啊都是底部的window,document,jQuery的映射;方便内部直接调用;
//当然你不引用jq的话头部的$和底部的jQuery干掉;你若引用了更过的依赖可以依次添加;
//最后面的undefined可不写;最好写了;保证里面再出现的undefined是未定义的意思;不被其他东西赋值;
//好了下面是时候展现真正的技术了
//function前的!号(叹号)或者;(分号)这不是写错了,为了防止那个二货写的js结束没有分号;而可能发生报错
/*
;function(win,doc,$,undefined){
}(window,document,jQuery)或者写在一个闭包里(function(){}())
*/
(function (win, doc, undefined) {
//我们随便写一个插件吧 比如你要点击按钮 添加input的值到 p里
var addHtml = function (demo, btn) {//插件名,调用的时候直接new一下插件名就行了并传参数或者传对象(一般这个函数名手写字母大写比较好,构造函数嘛,其实也是函数)
//很明显我要传id名;这里传什么都可以的其实;
this.p = doc.getElementById(demo);//为什么把获取的id传给this.p呢?this的指向为调用的实例;我们此时姑且认为this就指向这个函数;因为这样我们之后再想获取这个p就可以直接用this.p了好吗;而不是在document.getElementById(。。)
this.btn = doc.getElementById(btn);
this.Input = this.p.getElementsByTagName("input");//既然找到了p我们在找下p下面的input;当然你要不input用获取id的形式传参数我没有意见
this.num = 0;//你也可以写一些其他的默认的东西;比如默认的变量啦;方便下面调用;这里写了什么都不会报错;只是有用没用的问题这行可以忽略
this.author = "lianxiaozhuang";
this.init();//执行下你下面写的函数吧;你想想;如果整个插件没有执行函数;多不好;一堆方法function就不调用;对;这里是调用的时候最开始执行的函数
}
//;给构造函数addHtml对象原型里添加属性(方法)
addHtml.prototype = {//给函数写方法;这里可能不止一个函数;你还记得你在全局里写一个个的function吗;贼乱;
//找也不好找;把一个个函数都写到对象的属性里;调用函数就直接调用对象的属性;
constructor: addHtml,//构造器指向构造函数;这行其实不写没啥毛病;不过有时候防止构造器指向Object的情况;你还是装逼写上吧;
init: function () {//这里的init;你也可以写成 nimade:function(){ }都没有问题;就是在addHtml函数里this.init();执行下;你明白了这里的this了吧;整个插件里this都是只得这个函数(实例);除非你又引入了其他的函数里的(其他函数里的可能指向就是window了)
var _self = this;////把this保存下来防止在局部函数内部取不到(局部函数内部取得this可能是别的)
this.btn.onclick = function () {
var _val = _self.Input.value;
var tempp = doc.createElement("p");//创建临时p存放获取input的值
tempp.innerHTML = _val;
//console.log(tempp);
_self.p.appendChild(tempp);
_self.setColor();//你把所有方法都写在init里绝对没问题;还是那句话;说好的松耦合呢;说好的不写一堆堆的function一层层乱套呢
};
},
setColor: function () {
//console.log(this.p)
this.p.style.color = "red"
}/*,
otherFun(){
//当然你还可以扩展其他方法;这些方法之间都可以互相调用;
只要用this.方法名 就行了;如果在取不到this比如上面的click函数中的this指向点击的button;只要在写var _self = this;就可以用_self 代替this(函数实例)了;当然_self 也可以写成别的 比如$this等自定义的
}*/
}
win.addHtml = addHtml;//把这个对象附给window底下的 名字叫addHtml的对象;要不你调用的时候 new addHtml() 怕在window的环境下找不到;
}(window, document))
《/script》
《script type="text/javascript"》
new addHtml("demo-1", "add-1");//这里是实例1调用插件的代码
new addHtml("demo-2", "add-2"); //这里是实例2调用插件的代码
//是不是明白为什么要写插件了;要封装;两个相同组件即使有相同的class名在dom操作的时候也不会相互冲突;因为他们都new出来了个自的实例;有自己的this;有自己的一套方法了(其实方法都在原型里是公用的;操作各自的dom)
《/script》
《!--这里是最简单的插件写法;当然还有传对象参数的插件等等。。--》
《/body》
《/html》相关推荐:
Jquery操作Select 简单方便 一个js插件搞定_jquery
js实现md5加密插件代码分享
JS代码实现瀑布流插件
怎样用原生JS封装自己需要的插件
今天想和大家讨论一下我们在工作中对于插件的需求,我们写代码,并不是所有的业务或者逻辑代码都要抽出来复用。首先,我们得看一下是否需要将一部分经常重复的代码抽象出来,写到一个单独的文件中为以后再次使用。再看一下我们的业务逻辑是否可以为团队服务。插件不是随手就写成的,而是根据自己业务逻辑进行抽象。没有放之四海而皆准的插件,只有对插件,之所以叫做插件,那么就是开箱即用,或者我们只要添加一些配置参数就可以达到我们需要的结果。如果都符合了这些情况,我们才去考虑做一个插件。
插件封装的条件
一个可复用的插件需要满足以下条件:
插件自身的作用域与用户当前的作用域相互独立,也就是插件内部的私有变量不能影响使用者的环境变量;
插件需具备默认设置参数;
插件除了具备已实现的基本功能外,需提供部分API,使用者可以通过该API修改插件功能的默认参数,从而实现用户自定义插件效果;
插件支持链式调用;
插件需提供监听入口,及针对指定元素进行监听,使得该元素与插件响应达到插件效果。
关于插件封装的条件,可以查看一篇文章:原生JavaScript插件编写指南
而我想要说明的是,如何一步一步地实现我的插件封装。所以,我会先从简单的方法函数来做起。
插件的外包装
用函数包装
所谓插件,其实就是封装在一个闭包中的一种函数集。我记得刚开始写js的时候,我是这样干的,将我想要的逻辑,写成一个函数,然后再根据不同需要传入不同的参数就可以了。
比如,我想实现两个数字相加的方法:
function add(n1,n2) {
return n1 + n2;
}
// 调用
add(1,2)
// 输出:3这就是我们要的功能的简单实现。如果仅仅只不过实现这么简单的逻辑,那已经可以了,没必要弄一些花里胡哨的东西。js函数本身就可以解决绝大多数的问题。不过我们在实际工作与应用中,一般情况的需求都是比较复杂得多。
如果这时,产品来跟你说,我不仅需要两个数相加的,我还要相减,相乘,相除,求余等等功能。这时候,我们怎么办呢?
当然,你会想,这有什么难的。直接将这堆函数都写出来不就完了。然后都放在一个js文件里面。需要的时候,就调用它就好了。
// 加
function add(n1,n2) {
return n1 + n2;
}
// 减
function sub(n1,n2) {
return n1 - n2;
}
// 乘
function mul(n1,n2) {
return n1 * n2;
}
// 除
function div(n1,n2) {
return n1 / n2;
}
// 求余
function sur(n1,n2) {
return n1 % n2;
}OK,现在已经实现我们所需要的所有功能。并且我们也把这些函数都写到一个js里面了。如果是一个人在用,那么可以很清楚知道自己是否已经定义了什么,并且知道自己写了什么内容,我在哪个页面需要,那么就直接引入这个js文件就可以搞定了。
不过,如果是两个人以上的团队,或者你与别人一起协作写代码,这时候,另一个人并不知道你是否写了add方法,这时他也定义了同样的add方法。那么你们之间就会产生命名冲突,一般称之为变量的 全局污染
用全局对象包装
为了解决这种全局变量污染的问题。这时,我们可以定义一个js对象来接收我们这些工具函数。
var plugin = {
add: function(n1,n2){...},//加
sub: function(n1,n2){...},//减
mul: function(n1,n2){...},//乘
div: function(n1,n2){...},//除
sur: function(n1,n2){...} //余
}
// 调用
plugin.add(1,2)上面的方式,约定好此插件名为plugin,让团队成员都要遵守命名规则,在一定程度上已经解决了全局污染的问题。在团队协作中只要约定好命名规则了,告知其它同学即可以。当然不排除有个别人,接手你的项目,并不知道此全局变量已经定义,则他又定义了一次并赋值,这时,就会把你的对象覆盖掉。当然,可能你会这么干来解决掉命名冲突问题:
if(!plugin){ //这里的if条件也可以用: (typeof plugin == ’undefined’)
var plugin = { // 以此写你的函数逻辑 }
}或者
var plugin;
if(!plugin){
plugin = {
// ...
}
}这样子,就不会存在命名上的冲突了。
基本上,这就可以算是一个插件了。解决了全局污染问题,方法函数可以抽出来放到一单独的文件里面去。
利用闭包包装
上面的例子,虽然可以实现了插件的基本上的功能。不过我们的plugin对象,是定义在全局域里面的。我们知道,js变量的调用,从全局作用域上找查的速度会比在私有作用域里面慢得多得多。所以,我们最好将插件逻辑写在一个私有作用域中。
实现私有作用域,最好的办法就是使用闭包。可以把插件当做一个函数,插件内部的变量及函数的私有变量,为了在调用插件后依旧能使用其功能,闭包的作用就是延长函数(插件)内部变量的生命周期,使得插件函数可以重复调用,而不影响用户自身作用域。
故需将插件的所有功能写在一个立即执行函数中:
;(function(global,undefined) {
var plugin = {
add: function(n1,n2){...}
...
}
// 最后将插件对象暴露给全局对象
’plugin’ in global && global.plugin = plugin;
})(window);对上面的代码段传参问题进行解释一下:
在定义插件之前添加一个分号,可以解决js合并时可能会产生的错误问题;
undefined在老一辈的浏览器是不被支持的,直接使用会报错,js框架要考虑到兼容性,因此增加一个形参undefined,就算有人把外面的 undefined 定义了,里面的 undefined 依然不受影响;
把window对象作为参数传入,是避免了函数执行的时候到外部去查找。
其实,我们觉得直接传window对象进去,我觉得还是不太妥当。我们并不确定我们的插件就一定用于浏览器上,也有可能使用在一些非浏览端上。所以我们还可以这么干,我们不传参数,直接取当前的全局this对象为作顶级对象用。
;(function(global,undefined) {
"use strict" //使用js严格模式检查,使语法更规范
var _global;
var plugin = {
add: function(n1,n2){...}
...
}
// 最后将插件对象暴露给全局对象
_global = (function(){ return this || (0, eval)(’this’); }());
!(’plugin’ in _global) && (_global.plugin = plugin);
}());如此,我们不需要传入任何参数,并且解决了插件对环境的依事性。如此我们的插件可以在任何宿主环境上运行了。
关于立即自执行函数,有两种写法:
/ 写法一
(function(){})()//
写法二
(function(){}())
使用模块化的规范包装
虽然上面的包装基本上已经算是ok了的。但是如果是多个人一起开发一个大型的插件,这时我们要该怎么办呢?多人合作,肯定会产生多个文件,每个人负责一个小功能,那么如何才能将所有人开发的代码集合起来呢?这是一个讨厌的问题。要实现协作开发插件,必须具备如下条件:
每功能互相之间的依赖必须要明确,则必须严格按照依赖的顺序进行合并或者加载
每个子功能分别都要是一个闭包,并且将公共的接口暴露到共享域也即是一个被主函数暴露的公共对象
关键如何实现,有很多种办法。最笨的办法就是按顺序加载js
《script type="text/javascript" src="part1.js"》《/script》
《script type="text/javascript" src="part2.js"》《/script》
《script type="text/javascript" src="part3.js"》《/script》...《script type="text/javascript" src="main.js"》《/script》但是不推荐这么做,这样做与我们所追求的插件的封装性相背。
不过现在前端界有一堆流行的模块加载器,比如require、seajs,或者也可以像类似于Node的方式进行加载,不过在浏览器端,我们还得利用打包器来实现模块加载,比如browserify。不过在此不谈如何进行模块化打包或者加载的问题,如有问题的同学可以去上面的链接上看文档学习。
为了实现插件的模块化并且让我们的插件也是一个模块,我们就得让我们的插件也实现模块化的机制。
我们实际上,只要判断是否存在加载器,如果存在加载器,我们就使用加载器,如果不存在加载器。我们就使用顶级域对象。
if (typeof module !== "undefined" && module.exports) {
module.exports = plugin;
} else if (typeof define === "function" && define.amd) {
define(function(){return plugin;});
} else {
_globals.plugin = plugin;
}这样子我们的完整的插件的样子应该是这样子的:
// plugin.js
;(function(undefined) {
"use strict"
var _global;
var plugin = {
add: function(n1,n2){ return n1 + n2; },//加
sub: function(n1,n2){ return n1 - n2; },//减
mul: function(n1,n2){ return n1 * n2; },//乘
div: function(n1,n2){ return n1 / n2; },//除
sur: function(n1,n2){ return n1 % n2; } //余
}
// 最后将插件对象暴露给全局对象
_global = (function(){ return this || (0, eval)(’this’); }());
if (typeof module !== "undefined" && module.exports) {
module.exports = plugin;
} else if (typeof define === "function" && define.amd) {
define(function(){return plugin;});
} else {
!(’plugin’ in _global) && (_global.plugin = plugin);
}
}());我们引入了插件之后,则可以直接使用plugin对象。
with(plugin){
console.log(add(2,1)) // 3
console.log(sub(2,1)) // 1
console.log(mul(2,1)) // 2
console.log(div(2,1)) // 2
console.log(sur(2,1)) // 0
}插件的API
插件的默认参数
我们知道,函数是可以设置默认参数这种说法,而不管我们是否传有参数,我们都应该返回一个值以告诉用户我做了怎样的处理,比如:
function add(param){
var args = !!param ? Array.prototype.slice.call(arguments) : ;
return args.reduce(function(pre,cur){
return pre + cur;
}, 0);
}
console.log(add()) //不传参,结果输出0,则这里已经设置了默认了参数为空数组console.log(add(1,2,3,4,5)) //传参,结果输出15则作为一个健壮的js插件,我们应该把一些基本的状态参数添加到我们需要的插件上去。
假设还是上面的加减乘除余的需求,我们如何实现插件的默认参数呢?道理其实是一样的。
// plugin.js
;(function(undefined) {
"use strict"
var _global;
function result(args,fn){
var argsArr = Array.prototype.slice.call(args);
if(argsArr.length 》 0){
return argsArr.reduce(fn);
} else {
return 0;
}
}
var plugin = {
add: function(){
return result(arguments,function(pre,cur){
return pre + cur;
});
},//加
sub: function(){
return result(arguments,function(pre,cur){
return pre - cur;
});
},//减
mul: function(){
return result(arguments,function(pre,cur){
return pre * cur;
});
},//乘
div: function(){
return result(arguments,function(pre,cur){
return pre / cur;
});
},//除
sur: function(){
return result(arguments,function(pre,cur){
return pre % cur;
});
} //余
}
// 最后将插件对象暴露给全局对象
_global = (function(){ return this || (0, eval)(’this’); }());
if (typeof module !== "undefined" && module.exports) {
module.exports = plugin;
} else if (typeof define === "function" && define.amd) {
define(function(){return plugin;});
} else {
!(’plugin’ in _global) && (_global.plugin = plugin);
}
}());
// 输出结果为:
with(plugin){
console.log(add()); // 0
console.log(sub()); // 0
console.log(mul()); // 0
console.log(div()); // 0
console.log(sur()); // 0
console.log(add(2,1)); // 3
console.log(sub(2,1)); // 1
console.log(mul(2,1)); // 2
console.log(div(2,1)); // 2
console.log(sur(2,1)); // 0
}实际上,插件都有自己的默认参数,就以我们最为常见的表单验证插件为例:validate.js
(function(window, document, undefined) {
// 插件的默认参数
var defaults = {
messages: {
required: ’The %s field is required.’,
matches: ’The %s field does not match the %s field.’,
"default": ’The %s field is still set to default, please change.’,
valid_email: ’The %s field must contain a valid email address.’,
valid_emails: ’The %s field must contain all valid email addresses.’,
min_length: ’The %s field must be at least %s characters in length.’,
max_length: ’The %s field must not exceed %s characters in length.’,
exact_length: ’The %s field must be exactly %s characters in length.’,
greater_than: ’The %s field must contain a number greater than %s.’,
less_than: ’The %s field must contain a number less than %s.’,
alpha: ’The %s field must only contain alphabetical characters.’,
alpha_numeric: ’The %s field must only contain alpha-numeric characters.’,
alpha_dash: ’The %s field must only contain alpha-numeric characters, underscores, and dashes.’,
numeric: ’The %s field must contain only numbers.’,
integer: ’The %s field must contain an integer.’,
decimal: ’The %s field must contain a decimal number.’,
is_natural: ’The %s field must contain only positive numbers.’,
is_natural_no_zero: ’The %s field must contain a number greater than zero.’,
valid_ip: ’The %s field must contain a valid IP.’,
valid_base64: ’The %s field must contain a base64 string.’,
valid_credit_card: ’The %s field must contain a valid credit card number.’,
is_file_type: ’The %s field must contain only %s files.’,
valid_url: ’The %s field must contain a valid URL.’,
greater_than_date: ’The %s field must contain a more recent date than %s.’,
less_than_date: ’The %s field must contain an older date than %s.’,
greater_than_or_equal_date: ’The %s field must contain a date that\’s at least as recent as %s.’,
less_than_or_equal_date: ’The %s field must contain a date that\’s %s o
使用JS如何实现瀑布流插件
本篇文章给大家详细分析了一个原生JS实现瀑布流插件以及代码相关讲解,对此有兴趣的读者们参考学习下吧。
瀑布流布局中的图片有一个核心特点—等宽不定等高,瀑布流布局在国内网网站都有一定规模的使用,比如pinterest、花瓣网等等。那么接下来就基于这个特点开始瀑布流探索之旅。
基础功能实现
首先我们定义好一个有 20 张图片的容器,
《body》
《style》
#waterfall {
position: relative;
}
.waterfall-box {
float: left;
width: 200px;
}
《/style》
《/body》
《p id="waterfall"》
《img src="images/1.png" class="waterfall-box"》
《img src="images/2.png" class="waterfall-box"》
《img src="images/3.png" class="waterfall-box"》
《img src="images/4.png" class="waterfall-box"》
《img src="images/5.png" class="waterfall-box"》
《img src="images/6.png" class="waterfall-box"》
...
《/p》
由于未知的 css 知识点,丝袜最长的妹子把下面的空间都占用掉了。。
接着正文,假如如上图,每排有 5 列,那第 6 张图片应该出现前 5 张图片哪张的下面呢?当然是绝对定位到前 5 张图片高度最小的图片下方。
那第 7 张图片呢?这时候把第 6 张图片和在它上面的图片当作是一个整体后,思路和上述是一致的。代码实现如下:
Waterfall.prototype.init = function () {
...
const perNum = this.getPerNum() // 获取每排图片数
const perList = // 存储第一列的各图片的高度
for (let i = 0; i 《 perNum; i++) {
perList.push(imgList.offsetHeight)
}
let pointer = this.getMinPointer(perList) // 求出当前最小高度的数组下标
for (let i = perNum; i 《 imgList.length; i++) {
imgList.style.position = ’absolute’ // 核心语句
imgList.offsetLeft}px`
imgList}px`
perList.offsetHeight // 数组最小的值加上相应图片的高度
pointer = this.getMinPointer(perList)
}
}细心的朋友也许发现了代码中获取图片的高度用到了 offsetHeight 这个属性,这个属性的高度之和等于图片高度 + 内边距 + 边框,正因为此,我们用了 padding 而不是 margin 来设置图片与图片之间的距离。此外除了offsetHeight 属性,此外还要理解 offsetHeight、clientHeight、offsetTop、scrollTop 等属性的区别,才能比较好的理解这个项目。css 代码简单如下:
.waterfall-box {
float: left;
width: 200px;
padding-left: 10px;
padding-bottom: 10px;
}scroll、resize 事件监听的实现
实现了初始化函数 init 以后,下一步就要实现对 scroll 滚动事件进行监听,从而实现当滚到父节点的底部有源源不断的图片被加载出来的效果。这时候要考虑一个点,是滚动到什么位置时触发加载函数呢?这个因人而异,我的做法是当满足 父容器高度 + 滚动距离 》 最后一张图片的 offsetTop 这个条件,即橙色线条 + 紫色线条 》 蓝色线条时触发加载函数,代码如下:
window.onscroll = function() {
// ...
if (scrollPX + bsHeight 》 imgList.offsetTop) {// 浏览器高度 + 滚动距离 》 最后一张图片的 offsetTop
const fragment = document.createDocumentFragment()
for(let i = 0; i 《 20; i++) {
const img = document.createElement(’img’)
img.setAttribute(’src’, `images/${i+1}.png`)
img.setAttribute(’class’, ’waterfall-box’)
fragment.appendChild(img)
}
$waterfall.appendChild(fragment)
}
}因为父节点可能自定义节点,所以提供了对监听 scroll 函数的封装,代码如下:
proto.bind = function () {
const bindScrollElem = document.getElementById(this.opts.scrollElem)
util.addEventListener(bindScrollElem || window, ’scroll’, scroll.bind(this))
}
const util = {
addEventListener: function (elem, evName, func) {
elem.addEventListener(evName, func, false)
},
}resize 事件的监听与 scroll 事件监听大同小异,当触发了 resize 函数,调用 init 函数进行重置就行。
使用发布-订阅模式和继承实现监听绑定
既然以开发插件为目标,不能仅仅满足于功能的实现,还要留出相应的操作空间给开发者自行处理。联想到业务场景中瀑布流中下拉加载的图片一般都来自 Ajax 异步获取,那么加载的数据必然不能写死在库里,期望能实现如下调用(此处借鉴了 waterfall 的使用方式),
const waterfall = new Waterfall({options})
waterfall.on("load", function () {
// 此处进行 ajax 同步/异步添加图片
})观察调用方式,不难联想到使用发布/订阅模式来实现它,关于发布/订阅模式,之前在 Node.js 异步异闻录 有介绍它。其核心思想即通过订阅函数将函数添加到缓存中,然后通过发布函数实现异步调用,下面给出其代码实现:
function eventEmitter() {
this.sub = {}
}
eventEmitter.prototype.on = function (eventName, func) { // 订阅函数
if (!this.sub) {
this.sub
}
this.sub.push(func) // 添加事件监听器
}
eventEmitter.prototype.emit = function (eventName) { // 发布函数
const argsList = Array.prototype.slice.call(arguments, 1)
for (let i = 0, length = this.sub.length; i 《 length; i++) {
this.sub.apply(this, argsList) // 调用事件监听器
}
}接着,要让 Waterfall 能使用发布/订阅模式,只需让 Waterfall 继承 eventEmitter 函数,代码实现如下:
function Waterfall(options = {}) {
eventEmitter.call(this)
this.init(options) // 这个 this 是 new 的时候,绑上去的
}
Waterfall.prototype = Object.create(eventEmitter.prototype)
Waterfall.prototype.constructor = Waterfall继承方式的写法吸收了基于构造函数继承和基于原型链继承两种写法的优点,以及使用 Object.create 隔离了子类和父类,关于继承更多方面的细节,可以另写一篇文章了,此处点到为止。
小优化
为了防止 scroll 事件触发多次加载图片,可以考虑用函数防抖与节流实现。在基于发布-订阅模式的基础上,定义了个 isLoading 参数表示是否在加载中,并根据其布尔值决定是否加载,代码如下:
let isLoading = false
const scroll = function () {
if (isLoading) return false // 避免一次触发事件多次
if (scrollPX + bsHeight 》 imgList.offsetTop) { // 浏览器高度 + 滚动距离 》 最后一张图片的 offsetTop
isLoading = true
this.emit(’load’)
}
}
proto.done = function () {
this.on(’done’, function () {
isLoading = false
...
})
this.emit(’done’)
}这时候需要在调用的地方加上 waterfall.done, 从而告知当前图片已经加载完毕,代码如下:
const waterfall = new Waterfall({})
waterfall.on("load", function () {
// 异步/同步加载图片
waterfall.done()
})上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
Vue组件和Route的生命周期(详细教程)
利用SpringMVC解决vue跨域请求
webpack 4.0.0-beta.0版本新特性(详细教程)

更多文章:
fontcreator怎么导入字体(如何在FontCreator中加入我想要的字)
2025年7月10日 20:00
wordpress主题二次元(怎么用wordpress搭建情侣博客怎么自己弄情侣主题求达人详细赐教!)
2025年7月4日 02:00
腾讯会议怎么向主持人询问后台记录并保存?手机储存内容会被APP浏览并上传保存APP后台服务器吗连着WIFI
2025年7月21日 04:00
正则高考复读学校(2022南京高三复读有哪些学校 南京复读学校名单)
2025年8月7日 13:30
verify例句(please-slide-to-verify是什么意思)
2025年12月5日 06:45
false的副词(英语试卷判断对错,T和F,哪个是对,那个是错)
2026年7月29日 00:00
go语言需要编译吗(Go语言能在安卓运行吗 Go是脚本语言还是汇编)
2026年3月18日 20:00
有哪些js框架(web前端三大主流框架是什么 都有哪些功能)
2025年9月22日 16:30
springfestival六年级作文(六年级过春节英语作文300字)
2026年2月27日 20:45
在java语言中下列说法正确的是(Java语言说法正确的有())
2026年9月9日 20:30
edittext下划线颜色(android 如何使EditText中不同行显示不同的颜色(两种颜色))
2026年5月15日 11:30
帝国cms手机端(请教帝国cms中的手机端同步插件bug 修复方法)
2026年1月18日 22:15











