https://github.com/zcfan/sket... 重写了本文的初步功能实现,支持一个页面多个画图板。但为简单起见,本文保持不变。
简单的说一个 jQuery 插件只是我们拿来扩展 jQuery prototype 对象的一个方法。通过扩展 prototype 对象,我们可以让所有的 jQuery 对象继承我们添加的方法。
下面我们以一个画图板插件为目标,完成后它将能够把一个 div 标记扩展成最基本的画图板。
从最简单开始,我们要做的第一件事是给选中的div
加一个边框,好让用户能看到画板的区域。
创建 index.html 文件,引入 jQuery ,然后创建并引入我们的插件文件。尽管只是一个例子,但规范命名还是个好习惯,就叫做jquery.sketchpad.js
好了
<!-- file: index.html -->
<!DOCTYPE HTML>
<html>
<head>
<script src="http://cdn.bootcss.com/jquery/3.1.0/jquery.min.js"></script>
<script src="jquery.sketchpad.js"></script>
<style>
#sketchpad {
width: 200px;
height: 200px;
}
</style>
</head>
<body>
<div id="sketchpad"></div>
</body>
</html>
// file: jquery.sketchpad.js
$.fn.sketchpad = function() {
this.css('border', '1px solid #000');
};
// 使用
$('.sketchpad').sketchpad(); // 给所有.sketchpad加上边框
如上,想要添加一个名为 sketchpad 的插件,只需要给 $.fn
对象添加一个新方法就可以了。然后这个方法就可以在所有的 jQuery 对象上调用。
刷新页面,应该能够看到一个 200x200 的黑框
上面的代码能工作,但是还欠缺了很多必要的东西。jQuery的一个特色就是允许链式调用,它使你可以对一个选择器选中的元素连着执行许多操作。
这个特性的实现方式是让所有的 jQuery 方法都返回一开始的 jQuery 对象。(有一些例外,比如.width()
如果不提供参数的话就会返回所选元素的宽度,并且不可链式调用)
因此想让我们的插件能够支持链式调用只需要多一行代码:
// file: jquery.sketchpad.js
$.fn.sketchpad = function() {
this.css('border', '1px solid #000');
return this; // 返回该 jQuery 对象以支持链式调用
};
// 使用
$('.sketchpad').sketchpad().hide(); // 支持链式调用
$
作为一个简写化名确实非常方便,但是在实际使用中,总免不了会与其它 js 代码产生冲突。这时我们会需要调用jQuery.noConflict()
让jquery不再使用$
化名以避免冲突。
这个时候,我们前面的插件就会出问题,因为它编写的时候用到了$
化名。为了能够和其它的诸多插件友好相处,并且能够继续使用方便的$
,我们需要把所有代码扔进一个“立即执行函数的表达式”里,传入jQuery
作为实参,形参处命名为$
:
// file: jquery.sketchpad.js
(function($){
$.fn.sketchpad = function() {
this.css('border', '1px solid #000');
return this;
};
}(jQuery));
这样做还有另一个重要的原因,加入这样一个函数还能允许我们引入一个私有的变量作用域,不至于插件里的一些变量污染公共空间。比如说我们现在要创建一个 canvas
对象用来画图,就可以这样做:
// file: jquery.sketchpad.js
(function($){
// 本插件的私有函数
function createCanvas(width, height) {
var canvas = $('<canvas></canvas>');
canvas[0].width = width;
canvas[0].height = height;
return canvas;
}
$.fn.sketchpad = function() {
this.css('border', '1px solid #000');
var canvas = createCanvas(this.width(), this.height());
var ctx = canvas[0].getContext('2d'); // 获得 context 用于画图
this.append(canvas);
return this;
};
}(jQuery));
我们创建了一个新的私有函数 createCanvas 用于创建画布,避免将冗长的初始化代码堆在主函数里。
// jquery.sketchpad.js
(function($){
function createCanvas(width, height) {
var canvas = $('<canvas></canvas>');
canvas[0].width = width;
canvas[0].height = height;
return canvas;
}
$.fn.sketchpad = function() {
this.css('border', '1px solid #000');
var canvas = createCanvas(this.width(), this.height());
var ctx = canvas[0].getContext('2d');
this.append(canvas);
var isDrawing = false;
var offset = {
left: this[0].offsetLeft,
top: this[0].offsetTop
}
var pos
var prevX = 0,
prevY = 0,
currX = 0,
currY = 0;
canvas.mousedown(mousedown);
canvas.mouseup(mouseup);
canvas.mousemove(mousemove);
function calXY(e) {
return {
x: e.clientX - offset.left,
y: e.clientY - offset.top
};
}
function mousedown(e) {
var pos = calXY(e);
prevX = pos.x;
prevY = pos.y;
isDrawing = true;
}
function mouseup(e) {
isDrawing = false;
}
function mousemove(e) {
if (!isDrawing) return;
var pos = calXY(e);
currX = pos.x;
currY = pos.y;
ctx.beginPath();
ctx.moveTo(prevX, prevY);
ctx.lineTo(currX, currY);
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.stroke();
ctx.closePath();
prevX = currX;
prevY = currY;
}
return this;
};
}(jQuery));
本插件仅作为范例,功能较为简单,只是使用鼠标事件以及 canvas 画线。到此已经实现了开头说明的功能,但仍然可以继续扩展下去:保存载入、橡皮擦、色板甚至滤镜直至成为一个可以真正投入使用的插件。但随着插件发展与复杂度的增加,还有许多其他的地方需要注意。
编写插件时应该只占用$.fn
的一个位置。因为其它的插件也都在往这里塞东西,只占用一个名字能够避免我们的插件覆盖别人的名字或者被别人覆盖。举例说明,这样做不好:
(function( $ ) {
$.fn.openPopup = function() {
// Open popup code.
};
$.fn.closePopup = function() {
// Close popup code.
};
}( jQuery ));
这样就好多了,只占用一个名字,并且使用参数来调整插件的行为:
(function( $ ) {
$.fn.popup = function( action ) {
if ( action === "open") {
// Open popup code.
}
if ( action === "close" ) {
// Close popup code.
}
};
}( jQuery ));
each()
方法一个典型的 jQuery 对象会包含任意数量元素的引用,这也就是为什么 jQuery 对象经常是以集合的形式返回的。如果你想要对特定的某个元素做一些操作的话(比如获取数据属性,计算特定的位置),你就会需要使用each()
来枚举这些元素。
$.fn.myNewPlugin = function() {
return this.each(function() {
// 对每个单独的元素做一些操作
});
};
随着我们的插件以后越来越复杂,让它能够通过传入设置选项来自定义就十个好主意了。要做到这点有个最简单的办法(特别是当可配置项特别多的时候),那就是通过传入一个 object 。下面我们继续修改前面的 greenify 插件来接受配置:
(function($){
$.fn.greenify = function(options) {
// 实现默认设置的最简单方式
var settings = $.extend({
// 默认的设置
color: '#00ff00',
backgroundColor: 'white'
}, options);
return this.css({
color: settings.color,
backgroundColor: settings.backgroundColor
});
};
}(jQuery));
看完上面的内容之后,我们大概了解了一个简单的插件是怎么编写的,并且了解了一些社区总结的设计经验。当然这只是一个开端,还有许多进阶的内容没有介绍。比如参考资料中的下一篇 Advanced Plugin Concepts 就是进一步学习的好选择。
但今天已经够了,我们已经 get 了一个新技能,还有待实践来融汇贯通。欲速则不达,现在应该是开瓶可乐(你可以换啤酒),让自己沉浸在成就感中画幅骄傲的自画像的时候才对。
?干杯!~
我刚刚把代码扔到了 Github 上了,玩着蛮有意思的,打算做一个业余项目继续完善。然后一搜发现了好几个类似的项目。。。?https://github.com/zcfan/sket...