jQuery版本的图片剪切插件

网页应用程序应该实现,对丰富的内容提供简单易用的上传和操作方式。但是这样会给只有少数图片处理技能的用户造成很大的困难。图片剪切是最基本的图片处理技术,这个详细的教程讲解了如何在jQuery库上创建此功能的每一个步骤。

第一步:建立工作区间

         首先,我们要位我们这个教程建立一个工作区间,建立如图所示的文件层次结构,以及新建相应的空文件。

接着,你需要下载最新的javascript的jQuery(http://jquery.com/)库,并将其放在/resources/js文件夹中。本教程中使用的图片全部都放在/resources/images文件夹中,并且图片必须命名为example.jpg。最后一个文件outline.gif必须放在/resources/js/imageCrop文件夹中。

第二步:创建测试页面

          为了测试我们的插件,我们需要将其应用到一张图片上。当插件还未完成以前,我们会创建一个只包含一张图片的网页。

          打开创建的页面,并输入以下代码

    index.html

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="index.aspx.cs" Inherits="ImageCroppingPlugin.index" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<script src="Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>
<link href="resources/css/style.css" rel="stylesheet" type="text/css" />
<link href="resources/js/imageCrop/jquery.imagecrop.css" rel="stylesheet" type="text/css" />
<script src="resources/js/imageCrop/jquery.imagecrop.js" type="text/javascript"></script>

</head>
<body>
<form id="form1" runat="server">
<div id="wrapper">
<h1>
Jquery Image Croping plug-in
</h1>
<div class="image-decorator">
<img alt="Jquery Image Croping plug-in" height="360px" id="example" src="resources/images/example.JPG"
width
="480" /></div><!--图片修饰层-->
</div><!--#包裹层 -->
</form>
</body>
</html>

  style.css

 1 * {
2 margin : 0;
3 outline : 0;
4 padding : 0;
5 }
6 /*初始化网页样式*/
7 body {
8 background-color : #ededed;
9 color : #646464;
10 font-family : 'Verdana', 'Geneva', sans-serif;
11 font-size : 12px;
12 text-shadow : 0 1px 0 #ffffff;
13 }
14
15 h1 {
16 font-size : 24px;
17 font-weight : normal;
18 margin : 0 0 10px 0;
19 }
20
21 div#wrapper {
22 margin : 25px 25px 25px 25px;
23 }
24 /*选择id为wrapper的div*/
25 div.image-decorator {
26 -moz-border-radius : 5px 5px 5px 5px;/*针对火狐的浏览器的盒子的锐化*/
27 -moz-box-shadow : 0 0 6px #c8c8c8;/*针对火狐的浏览器的盒子的边框阴影处理*/
28 -webkit-border-radius : 5px 5px 5px 5px;/*WebKit 是一个开源的浏览器引擎*/
29 -webkit-box-shadow : 0 0 6px #c8c8c8;
30 background-color : #ffffff;
31 border : 1px solid #c8c8c8;
32 border-radius : 5px 5px 5px 5px;
33 box-shadow : 0 0 6px #c8c8c8;
34 display : inline-block;/*将对象呈递为内联对象,但是对象的内容作为块对象呈递。旁边的内联对象会被呈递在同一行内,允许空格。支持的浏览器有:Opera、Safari*/
35 height : 360px;
36 padding : 5px 5px 5px 5px;
37 width : 480px;
38 }

  以上我们通过改变背景颜色和设置一些基本的样式,使我们的页面更具有可观赏性。

第三步:写一个基本的jQuery插件

        让我们开始写一个基本的jQuery插件吧,在写之前,如果读者你从未有写jQuery插件的经历,建议先看看官方给出的插件教程(http://docs.jquery.com/Plugins/Authoring),这个是英文版的,中文版的没找的,写者打算翻译,敬请期待。

         打开/resources/js/imageCrop/jquery.imagecrop.js,并且加入如下图所示的js代码

//一般情况下请将所写的插件代码包含在‘(function($) { // 插件代码在这里 }) (jQuery);’
(function($) {
$.imageCrop
= function(object, customOptions) {};

$.fn.imageCrop
= function (customOptions) {
//Iterate over each object
//对每一个对象进行迭代
this.each(function () {
var currentObject = this,
image
= new Image();

//当对象加载完毕时,给予附加上imageCrop剪切的功能
image.onload = function () {
$.imageCrop(currentObject, customOptions);
};

//重设图片的地址,因为有时被缓存的图像不能被很快的加载
image.src = currentObject.src;
});

//除非你的插件返回一个确定的值,那么通常情况下要求函数返回‘this’关键字
//以此来保持编程的链式化
return this;
};
}) (jQuery);

  我们刚刚扩展了jQuery,通过在jQuery.fn这个对象上新添加一个方法属性。现在我们完成了对每一个对象进行迭代并当其加载完毕时为其附上imageCrop功能的基本插件。注意到被缓存的图片可能不会被很快的下载,所以重置了它的图片地址。

第四步:加入可定制的选择

          通过可以定制的选择,使得对用户来说有了更多的选择以及使插件更加具有灵活性。(备注:以下代码均按照顺序来的)

 //将插件的选项封装在一个常量对象中,远远好于传递一长串参数来传递。
//这样使得可以在插件默认的情况下进行扩展
var defaultOptions = {
allowMove:
true,
allowResize:
true,
allowSelect:
true,
minSelect: [
0, 0],
outlineOpacity:
0.5,
overlayOpacity:
0.5,
selectionPosition: [
0, 0],
selectionWidth:
0,
selectionHeight:
0
};

// 将选项设为默认选项
var options = defaultOptions;

// 然后将其和客户定制的选项合并
setOptions(customOptions);

 以上我们通过定义一个包含默认选项的数组,然后通过使用setOption函数来实现将默认选项和定制选项进行合并。现在让我们来写这个合并函数体

 // 将默认选项和客户定制的选项合并
function setOptions(customOptions) {
options
= $.extend(options, customOptions);
};

  $.extend()函数实现了将两个或者多个对象合并到第一个对象中的功能。

选项

下面的列表解释了插件中的每一个选项

  • allowMove – 指定选择区是否可以移动(默认的值是true.)
  • allowResize – 指定选择区是否可以被重新指定大小(默认的值是true)
  • allowSelect – 指定用户是否可以重新指定选择区(默认的值是true)
  • minSelect – 一个新的选择区最小的大小(默认的大小是[0, 0]
  • outlineOpacity – 轮廓的透明度(默认的值是0.5)
  • overlayOpacity – 覆盖层的透明度(默认的值是0.5)
  • selectionPosition – 选择区得位置(默认的是[0, 0]
  • selectionWidth – 选择区得宽度(默认的值是0)
  • selectionHeight –  选择区的长度(默认的值是0)

第五步:建立图像层

          这一步我们将改变文档的结构,以此来为下一步做准备:插件的表面

首先我们要初始化图像层,然后初始化图像包含层

/ 初始化图像层
var $image = $(object);

// 初始化一个图像支持层
var $holder = $('<div />')
.css({
position:
'relative'
})
.width($image.width())
.height($image.height());

// imag包含在holder层里面 .wrap()函数

$image.wrap($holder)
.css({
position:
'absolute'
});

  正如你所见,包含层和图像具有同样的大小并且包含层和图像是相对定位。然后我们使用.wrap函数使得图像包含在其中

在图像的上面是覆盖层:

//初始化一个覆盖层,并将其置于图像之上
var $overlay = $('<div id="image-crop-overlay" />')
.css({
opacity: options.overlayOpacity,
position:
'absolute'
})
.width($image.width())
.height($image.height())
.insertAfter($image);

  这个层同样和图像一样大,但是是绝对定位。我们从options.outlineOpacity得到透明度。这个元素拥有一个Id,所以我们可以改变通过插件的css来改变它的样式。在最后我们用.insertAfter($image)方法将覆盖层恰好放在图像层的下面。

下面一层是触犯器层

/ 初始化一个触发器层,并将其放在覆盖层的上面
var $trigger = $('<div />')
.css({
backgroundColor:
'#000000',
opacity:
0,
position:
'absolute'
})
.width($image.width())
.height($image.height())
.insertAfter($overlay);

  这一次对于用户来说是不可见的,但是它会处理一些事件。

接下来是边框层和选择层

 // 初始化一个边框层,将其放在触发器层的上面
var $outline = $('<div id="image-crop-outline" />')
.css({
opacity: options.outlineOpacity,
position:
'absolute'
})
.insertAfter($trigger);

// 初始化一个选择层,将其置于边框层的上面
var $selection = $('<div />')
.css({
background:
'url(' + $image.attr('src') + ') no-repeat',
position:
'absolute'
})
.insertAfter($outline);

  .attr()方法是用来返回某个特定的属性的值,我们用它来得到图像的地址,并将其作为选择层的背景

绝对定位在相对定位里面

 一个相对定位的元素可以控制绝对定位的元素,使得绝对定位的元素在相对定位的元素的里面。这也算为什么包含层是相对定位,而所有的它的子元素都是绝对定位

第六步:更新界面

      首先我们要初始为一些全局变量

 //初始化全局变量
var selectionExists,
selectionOffset
= [0, 0],
selectionOrigin
= [0, 0];

  selectionExists会告知我们是否存在着一个选择区域,selectionOffset会包含相对于起点的偏移量,selectionOrigin会包含选择区域的起点

     下面的条件用于当插件被加载时选择区域就存在

  //指示选择区域的大小是否比最小的大,然后再根据它来设定选择区域是否存在
if (options.selectionWidth > options.minSelect[0] &&
options.selectionHeight
> options.minSelect[1])
selectionExists
= true;
else
selectionExists
= false;

  现在我们将调用updateInterface来初始化插件的界面

 //第一次调用‘uploadInterface’函数来初始化插件的界面
updateInterface();

  

if (options.allowSelect)
//在触发器层的mousedown事件上绑定一个事件处理程序
$trigger.mousedown(setSelection);

  如果allowSelect为true,那么就调用.mousedown()。一个事件会绑定到.mousedown函数上,所以当用户点击鼠标时,那么setSelection()事件就会执行。

 //得到当前元素的偏移量
function getElementOffset(object) {
var offset = $(object).offset();

return [offset.left, offset.top];
};

//得到当前鼠标相对于图片的位置
function getMousePosition(event) {
var imageOffset = getElementOffset($image);

var x = event.pageX - imageOffset[0],
y
= event.pageY - imageOffset[1];

x
= (x < 0) ? 0 : (x > $image.width()) ? $image.width() : x;
y
= (y < 0) ? 0 : (y > $image.height()) ? $image.height() : y;

return [x, y];
};

上面的第一个函数 getElementOffset()的作用是返回特定对象的相对于文档的左部和顶部的坐标值,我们通过调用.offset()函数来得到这个值。第二个函数,getMousePosition(), 返回的是当期鼠标相对于图片的位置,所以这些值只能是在0和图片的宽和长之间。

下面就是实现更新各个层的功能了

  // 更新覆盖层
function updateOverlayLayer() {
$overlay.css({
display: selectionExists
? 'block' : 'none'
});
};

  这个函数通过判断selectionExists的值来确定覆盖层

 //更新触发器层
function updateTriggerLayer() {
$trigger.css({
cursor: options.allowSelect
? 'crosshair' : 'default'
});
};

  触发器层通过options.allowSelect来更改鼠标的手势。

  // 更新选择
function updateSelection() {
// 更新边框
$outline.css({
cursor:
'default',
display: selectionExists
? 'block' : 'none',
left: options.selectionPosition[
0],
top: options.selectionPosition[
1]
})
.width(options.selectionWidth)
.height(options.selectionHeight);

// 更新选择层
$selection.css({
backgroundPosition: (
-options.selectionPosition[0] - 1) + 'px ' + (-options.selectionPosition[1] - 1) + 'px',
cursor: options.allowMove
? 'move' : 'default',
display: selectionExists
? 'block' : 'none',
left: options.selectionPosition[
0] + 1,
top: options.selectionPosition[
1] + 1
})
.width((options.selectionWidth
- 2 > 0) ? (options.selectionWidth - 2) : 0)
.height((options.selectionHeight
- 2 > 0) ? (options.selectionHeight - 2) : 0);
};

首先,这个函数设置了边框层的鼠标手势、显示和大小属性.接着是对边框层,新的背景位置会使的图像实现无缝的重复。

现在,我们需要一个函数来更新我们的鼠标手势。例如,当我们做出选择时,无论我们在哪一个层都需要鼠标手势一直保持十字架形式

 //更新鼠标手势
function updateCursor(cursorType) {
$trigger.css({
cursor: cursorType
});

$outline.css({
cursor: cursorType
});

$selection.css({
cursor: cursorType
});
};

  下面这个功能是这一步的最后一个功能,我们需要用它来更新插件的在选择中,重绘大小中,释放和选择后不同情形中的界面。

  // 更新插件的界面
function updateInterface(sender) {
switch (sender) {
case 'setSelection':
updateOverlayLayer();
updateSelection();

break;
case 'resizeSelection':
updateSelection();
updateCursor(
'crosshair');

break;
case 'releaseSelection':
updateTriggerLayer();
updateOverlayLayer();
updateSelection();

break;
default:
updateTriggerLayer();
updateOverlayLayer();
updateSelection();
}
};

  正如你所见,updateInterface()过滤了一下情况并调用了我们需要的函数。

第七步,设置选择区域

       到现在为止,我们已经完成了关于插件定制选项和插件外观的设置,但是还没有任何用户和插件互动的的函数。现在就让我们首先来实现当图片被点击时设置新的选择项的函数吧。

 // 设置一个新的选择区域
function setSelection(event) {
// 阻止事件的默认动作
event.preventDefault();

// 防止事件被冒泡
event.stopPropagation();

// 绑定一个处理事件在mousemove事件上面
$(document).mousemove(resizeSelection);

// 绑定一个处理事件在mouseup事件上面
$(document).mouseup(releaseSelection);

// 告知一个选择区域已存在
selectionExists = true;

// 重设选择区域的大小
options.selectionWidth = 0;
options.selectionHeight
= 0;

// 得到选择区域的原来区域
selectionOrigin = getMousePosition(event);

// 设定它的位置
options.selectionPosition[0] = selectionOrigin[0];
options.selectionPosition[
1] = selectionOrigin[1];

//通过指定参数 更新需要更新的插件元素
updateInterface('setSelection');
};

     首先,setSelection函数调用了两个方法:event.preventDefault()和event.stopPropagation().这些阻止了默认的动作和其他被冒泡的父操作。.mousemove()方法在mousemove事件绑定了一个事件处理程序,这样当每一次当用户移动鼠标指针的时候就会调用resizeSelection方法。为了标示一个新的选择区域已经形成,selectionExists常量和selection size分别被置为true和0。接着,我们通过调用我们以前写的getMousePosition()方法来重设选择的开始区域,然后将其值传递给options.selectionPositon。最后,根据我们做出的改变来更新插件的界面。

第八步:重绘选择区域的大小

   在前面的步骤中,我们写了一个函数来设定新的选择区域,现在让我们一起来实现重绘的功能吧。

 //重绘现在的选择区域
function resizeSelection(event) {
// 阻止事件的默认动作
event.preventDefault();

// 防止事件被冒泡
event.stopPropagation();

var mousePosition = getMousePosition(event);

// 得到现在区域的大小
options.selectionWidth = mousePosition[0] - selectionOrigin[0];
options.selectionHeight
= mousePosition[1] - selectionOrigin[1];

if (options.selectionWidth < 0) {
options.selectionWidth
= Math.abs(options.selectionWidth);
options.selectionPosition[
0] = selectionOrigin[0] - options.selectionWidth;
}
else
options.selectionPosition[
0] = selectionOrigin[0];

if (options.selectionHeight < 0) {
options.selectionHeight
= Math.abs(options.selectionHeight);
options.selectionPosition[
1] = selectionOrigin[1] - options.selectionHeight;
}
else
options.selectionPosition[
1] = selectionOrigin[1];

//通过指定参数 更新需要更新的插件元素
updateInterface('resizeSelection');
};

 为了得到新的选择区域的大小,我们需要重新得到现在鼠标的位置。因为返回的值是相对于图片大小的,所以可能是负的值。它永远不会超过图片的大小。正如你所知,我们不能对一个元素的width和height属性运用负的值。为了解决这个问题,我们调用了Math.abs()这个函数来得到绝对值,然后重新定位了这个元素。

第九步:释放选择区域

终于到了最后一步了

        //释放当前选择区域
function releaseSelection(event) {
// 阻止事件的默认动作
event.preventDefault();

// 防止事件被冒泡
event.stopPropagation();

// 释放对‘mousemove’事件的绑定事件
$(document).unbind('mousemove');

// 释放对‘mousup’事件的绑定事件
$(document).unbind('mouseup');

// 更新选择区域的起始点
selectionOrigin[0] = options.selectionPosition[0];
selectionOrigin[
1] = options.selectionPosition[1];

//验证选择区域的大小是否比能接受的最小区域大然后更具验证设定选择区域是否存在
if (options.selectionWidth > options.minSelect[0] &&
options.selectionHeight
> options.minSelect[1])
selectionExists
= true;
else
selectionExists
= false;

//通过指定参数 更新需要更新的插件元素
updateInterface('releaseSelection');
};

 当选择区域被释放时,releaseSelection()函数通过调用.unbind()方法,将以前在setSelection()函数里面绑定的事件处理程序全部移调。接着,它更新了选择区域的原点,并且测试了选择区域是否满足最小的大小。

现在我们几乎完成了主要的功能。现在关闭这个js文件准备下一步吧!

第十步:插件的样式

打开下面的文件/resources/js/imageCrop/jquery.imagecrop.css ,并加入下面的css代码

div#image-crop-overlay {
background-color
: #ffffff;
overflow
: hidden;
}

div#image-crop-outline
{
background
: #ffffff;
overflow
: hidden;
}

第十一步:测试插件

为了测试插件你,需要在index页面中嵌入以下代码

<script type="text/javascript">
$(document).ready(
function() {
$(
'img#example').imageCrop({
overlayOpacity :
0.25
});
});
</script>

  接着就完成了,呵呵。

第十二步:等待........

由于不知怎样上传附件,请需要demo的园友们,发邮件到85770253@qq.com,呵呵。

现在的插件只实现了部分功能,还没有和服务器端进行交换,在后来的文章中,写作将翻译接下来的与服务器端交互的部分,敬请期待........Shawn

posted @ 2011-08-03 17:26  shawnXiao  Views(3091)  Comments(1Edit  收藏  举报