Code
尝试翻译的一篇文章,作者真的是手把手的教学,原文很酷,不过在这里保留原文的样式比较困难,我们还是关注文章内容吧:D。
如果有什么不对的地方还请大家回复告诉我,转载请注明出处。
原文:How to write a Mootools Class
在这篇文章,你将和我一起一步一步的写一个类。在这个例子里的幻灯片类,也在我们的通用类库里面,那里有更多本文没有提到的功能。
第一步:
// 每次我的类几乎都有一个同样的开端,我们就从这里开始。这个类有options,events,和一个初始化的部分。options和events不是必须的,但是除非我不打算复用代码,否则这个类都有options和events。
// 即使你只打算写写自己用的代码,还是要在你的类中提供这些挂钩,这将使你代码更加可复用。
var SimpleSlideShowDemo = new Class({
Implements: [Options, Events],
options: {
},
initialize: function(options){
this.setOptions(options);
}
});
第二步:
// 好了,我们正在写一个幻灯片类,那么在这样一个类中首先应该开始做什么?当然要有滑动,不是吗?此外,我们也要给用户(即创建这个类的实例的人,不是用浏览器观看效果的人,那样的人我会称之为观众)选项用来指定幻灯片从哪里开始,如果幻灯片会回到起点还要能知道它何时到达终点。
// 这些选项设置了默认值,但是用户可以修改它们。这个功能是Options类提供的。当我们实现Options的接口时,我们也就给我们的类增加了Options(TODO)中的约定和功能。我们能执行options中的this.setOptions来使用户修改选项的默认值,但是如果用户仅仅指定了一部分选项值,那么剩下的选项默认值不变。
// 重要的一点是注意选项是可选的。如果你的类运行需要一些参数,那么你就应该传递这些参数给你的类地初始化函数。在这里应该加入必要的滑动,除非我想晚些再加入滑动,那意味着我将创建一个没有滑动的幻灯片,然后我可以在晚些时候添加滑动,这使得滑动是可选的。
var SimpleSlideShowDemo = new Class({
Implements: [Options, Events],
options: {
slides: [],
startIndex: 0,
wrap: true
},
initialize: function(options){
//接下来我们设置options.即使用户不会传递参数,setOptions也会被传入一个null或者undefined。你也可以传递比这个对象更多的东西,不过我们这儿用不着。
this.setOptions(options);
//现在我们将用户传递的幻灯片添加到幻灯片的数组中,如果用户什么也没有传进来,那么就是一个空的幻灯片的数组,这样什么事情也不会发生。
this.addSlides(this.options.slides);
//如果有很多的幻灯片,我们就要显示出options中设置的第一个幻灯片
if(this.slides.length) this.showSlide(this.options.startIndex);
},
//设置一个数组成员用来存储那些幻灯片。我可以直接设置成this.options.slides,但是在这个例子中我打算通过我在下一步写的方法来操作这些幻灯片。有了这个独立的方法,用户就可以任意添加幻灯片了。
slides: []
});
第三步:
var SimpleSlideShowDemo = new Class({
Implements: [Options, Events],
options: {
slides: [],
startIndex: 0,
wrap: true
},
initialize: function(options){
this.setOptions(options);
this.addSlides(this.options.slides);
if(this.slides.length) this.showSlide(this.options.startIndex);
},
slides: [],
//好了,这次我们传递给addSlides方法来添加dom元素数组作为幻灯片,我们用.each方法来循环这个数组,并且将每个数组元素加入到幻灯片数组中去,就是this.slides这个属性。我们使用数组的.include,因而我们不需要复制一个副本出来。
//注意这里我通过$$来运行这个数组,如果已经是个集合那么什么都不会发生,这仅仅是返回集合,但是又不仅仅是一个集合,这个方法添加了MooTool的函数到这些数组上面。这意味着用户也可以选择传递一个选择器。
//译注:比如传递#myElement或者#myElement a.myClass之类的CSS选择器
addSlides: function(slides){
//我们使用了Array.each来迭代了幻灯片数组。在这里有一个重要的概念需要理解:this标记符。在一个类里面,this是对这个类的实体的引用。例如,"var SomeClass = new Class({});" 是一个类。 然后 "var instanceOfSomeClass = new SomeClass();" 创建了一个这个类的实体.
//我们使用this关键字来引用这个实体中的成员。this不是对类的引用,而是对这个实体的引用。
//在一个函数中,this引用的是这个函数,除非这个函数是一个类的方法(就像我们这个类的例子一样)。所以使用Array.each,你传递了两个参数,第一个是你希望在数组的每个元素上执行的函数,而另一个则是你想绑定this的这个对象。
//你需要这个绑定,因为你想执行的函数有this绑定到函数自身,而不是你的类。你仅仅需要指定这个绑定当你在这个函数中引用this的时候。如果你不打算引用你的类中的方法和属性,那么你不需要绑定任何东西(绑定了也不会有什么损害)。更多关于绑定(TODO)。
$$(slides).each(function(slide){
//我使用.include来将当前幻灯片加入到对象的slides属性,这将仅仅添加slides数组中原来不存在的幻灯片。
this.slides.include($(slide));
//在这里我们指定了绑定到类的this。
}, this);
},
//提供一个添加单个幻灯片的方法,它调用了上面的.addSlides来将这个幻灯片用$splat包装成数组后添加进去。
addSlide: function(slide){
this.addSlides($splat($(slide)));
}
});
第四步:
var SimpleSlideShowDemo = new Class({
Implements: [Options, Events],
options: {
slides: [],
startIndex: 0,
wrap: true
},
initialize: function(options){
this.setOptions(options);
this.addSlides(this.options.slides);
if(this.slides.length) this.showSlide(this.options.startIndex);
},
slides: [],
addSlides: function(slides){
$$(slides).each(function(slide){
this.slides.include($(slide));
//现在用户可以有办法来完成这个幻灯片显示。在这个类的真实例子(SimpleSlideShow(TODO) in CNET's common library)中,我让用户指定了dom元素数组用来向前和向后。在这个例子中,让我们来添加一个行为到幻灯片上,如果用户点击了幻灯片,我们循环向前。
//Element.addEvent(TODO)添加了用于这个元素的函数的事件句柄,但是默认this绑定到这个元素的函数,这样在这个Element.addEvent函数中,this意味着这个元素,可是我们需要它是这个我们正在编辑的类,所以当我们添加事件的时候不得不绑定我们的类到这个函数上。
//注意,不像Array.each,这里我们必须使用.bind方法,因为addEvent不接收第三个参数用来绑定。
slide.addEvent('click', this.cycleForward.bind(this));
}, this);
},
addSlide: function(slide){
this.addSlides($splat($(slide)));
}
});
第五步:
var SimpleSlideShowDemo = new Class({
Implements: [Options, Events],
options: {
slides: [],
startIndex: 0,
wrap: true
},
initialize: function(options){
this.setOptions(options);
this.addSlides(this.options.slides);
if(this.slides.length) this.showSlide(this.options.startIndex);
},
slides: [],
addSlides: function(slides){
$$(slides).each(function(slide){
this.slides.include($(slide));
slide.addEvent('click', this.cycleForward.bind(this));
}, this);
},
addSlide: function(slide){
this.addSlides($splat($(slide)));
},
//循环向前和循环向后。在这个短小的例子中,我实际没有定义一个方法来让用户可以循环向后,仅仅能循环向前,但是在真实的应用中我使用在options中的next/previous链接来让用户同时能做这两件事情。
cycleForward: function(){
//如果this.now已经被定义(当前可访问的幻灯片索引),那么我能继续增加,就可以显示下一张幻灯片。
if($chk(this.now) && this.now < this.slides.length-1) this.showSlide(this.now+1);
//如果this.now 已经被定义并且既不是null也不是0(最后一个),那么根据wrap,可以返回到0显示第一个幻灯片。
else if ((this.now) && this.options.wrap) this.showSlide(0);
//如果,理论上,this.now并没有定义因为我们以前从来没有显示过幻灯片,那么显示第一个参数设定。
else if(!$defined(this.now)) this.showSlide(this.options.startIndex);
//这个类没有用到下面的函数,但是还是将循环向后的函数写在这里了。这里假想循环向后被调用了最少一次(在你初始化这个类后调用),因此this.now有一个值。在真实的类中(SimpleSlideShow)我让用户传入那些dom元素的引用来向前和向后。
},
cycleBack: function(){
if(this.now > 0) this.showSlide(this.now-1);
else if(this.options.wrap) this.showSlide(this.slides.length-1);
}
});
第六步:
var SimpleSlideShowDemo = new Class({
Implements: [Options, Events],
options: {
slides: [],
startIndex: 0,
wrap: true
},
initialize: function(options){
this.setOptions(options);
this.addSlides(this.options.slides);
if(this.slides.length) this.showSlide(this.options.startIndex);
},
slides: [],
addSlides: function(slides){
$$(slides).each(function(slide){
this.slides.include($(slide));
slide.addEvent('click', this.cycleForward.bind(this));
}, this);
},
addSlide: function(slide){
this.addSlides($splat($(slide)));
},
cycleForward: function(){
if($chk(this.now) && this.now < this.slides.length-1) this.showSlide(this.now+1);
else if ((this.now) && this.options.wrap) this.showSlide(0);
else if(!$defined(this.now)) this.showSlide(this.options.startIndex);
},
cycleBack: function(){
if(this.now > 0) this.showSlide(this.now-1);
else if(this.options.wrap) this.showSlide(this.slides.length-1);
},
//显示一个幻灯片意味着我们设置css属性"display"为"block"给这个幻灯片元素,并且将其他的幻灯片元素设置为"none"。
showSlide: function(iToShow){
//如果传入的幻灯片索引在我们的可用的幻灯片中就显示这张幻灯片。假如这是第一次尝试显示幻灯片,this.now并没有被定义,那么仅仅显示当前幻灯片,如果已经被定义了,就需要隐藏当前的幻灯片(如果我们没有确定就直接执行.setStyle在未定义的变量上,那么就会出错)。
if(this.slides[iToShow]) {
if(this.slides[this.now]) this.slides[this.now].setStyle('display','none');
this.slides[iToShow].setStyle('display','block');
this.now = iToShow;
}
}
}
});
第七步:
var SimpleSlideShowDemo = new Class({
Implements: [Options, Events],
options: {
slides: [],
startIndex: 0,
wrap: true
},
initialize: function(options){
this.setOptions(options);
this.addSlides(this.options.slides);
if(this.slides.length) this.showSlide(this.options.startIndex);
},
slides: [],
addSlides: function(slides){
$$(slides).each(function(slide){
this.slides.include($(slide));
slide.addEvent('click', this.cycleForward.bind(this));
}, this);
},
addSlide: function(slide){
this.addSlides($splat($(slide)));
},
cycleForward: function(){
if($chk(this.now) && this.now < this.slides.length-1) this.showSlide(this.now+1);
else if ((this.now) && this.options.wrap) this.showSlide(0);
else if(!$defined(this.now)) this.showSlide(this.options.startIndex);
},
cycleBack: function(){
if(this.now > 0) this.showSlide(this.now-1);
else if(this.options.wrap) this.showSlide(this.slides.length-1);
},
//当然,仅仅显示幻灯片并不好玩。我们希望能够让这些幻灯片渐变。有几种方法能做到这种变迁,让我们来试着让它简单些。我们希望当前的幻灯片渐弱直到消失,还希望下一张幻灯片能够渐强显示。
showSlide: function(iToShow){
//以下几行你能看到我们设置了this.now来等于我们当前正在显示的幻灯片索引(iToShow)。这样我们的类就随时能够知道当前显示的幻灯片索引。
//但是我们还有个新的好主意:变迁。因为这个变迁需要花些时间,我们不能引用this.now,因为它在我们的变迁花费时间的时候已经变化了。在一秒中发生了很多,这就是为什么我定义了变量now,在this.now被污染的时候存储它。
var now = this.now;
var currentSlide = this.slides[now];
var slide = this.slides[iToShow];
//这里是一个闭包的例子。这个fadein方法,可以引用已经被声明的值,但是我们仅仅想在当前的幻灯片渐弱完成后再对下一张幻灯片渐强。在执行的时候,这里声明的函数能获得整个方法中的变量。所以即使函数在上一张幻灯片渐弱后被调用,它仍能调用这里定义的值。
//整个函数用来改变元素的样式为display:block,但是隐藏(透明),然后从透明到不透明渐变。
//注意这个函数之前没有被声明,其实我可以在下面的代码中匿名执行它。把这个函数声明在这里的原因是需要调用两次并且我不想重复代码。
var fadeIn = function (s){
s.setStyles({
display:'block',
visibility: 'visible',
opacity: 0
});
s.get('tween').start('opacity', 1);
}.bind(this);
//注意在上面我绑定this到这个函数来确保任何对此的调用都是在调用这个类的对象。
if(slide) {
//第一次这个方法被调用时还没有显示幻灯片,所以不需要渐弱效果。如果不是这种情况(不是第一次调用),我们将需要对当前幻灯片渐弱后再对下一张幻灯片渐强。
if($chk(now) && now != iToShow){
//我们渐弱当前的幻灯片后,使用Chain(TODO),执行我们的函数到渐强下一张幻灯片。
currentSlide.get('tween').start('opacity', 0).chain(function(){
//注意当前幻灯片已经被隐藏,我们设置它display:none。我们的透明度刚刚被设置成透明,但是在我们显示下一张幻灯片前,需要将当前的幻灯片从页面流中清出去。
currentSlide.setStyle('display', 'none');
//现在我们用上面的fadeIn函数来渐强下一张幻灯片。
fadeIn(slide);
//注意这里我们使用了.bind(this),因为在这个函数(我们传递给.chain的)里面,我们需要调用这个类的实体对象。一些方法让你传递一个绑定的对象,像Array.each或者Function.apply(TODO)。当这无法做到,你必须用.bind(this)来达到这个效果。
//译注:如果不.bind(this),currentSlide被作为this使用。
}.bind(this));
//如果变量now 是undefined或者指定的幻灯片已经显示,直接立刻渐强幻灯片,如果幻灯片已经显示,这个渐强函数什么也不会改变。
} else fadeIn(slide);
//现在我们保存this.now为当前正显示的幻灯片。因为我们在上面的函数链中调用了now(换句话说,我们在本来显示的幻灯片隐藏后调用了now),我们不能引用this.now,因为下一行将在函数链的代码执行完渐弱后立刻执行
this.now = iToShow;
}
}
});
第八步:
var SimpleSlideShowDemo = new Class({
Implements: [Options, Events],
options: {
slides: [],
startIndex: 0,
wrap: true
//Event(TODO)能使你添加自定义事件到你的类上。这真是非常强大并且能让你的代码更加的可复用。我推荐在你的代码中增加这些事件,onShow, onHide, onComplete, onStart, onError等等。这能让其他人(也包括你自己)约束这个类的行为在非侵扰性的方式上。你不需要有太多的事件。
//因为我们在这个类上实现了Options,所以我们继承了那个类的一些关于事件的方法。如果你有哪个选项参数是用“on”开头,当你调用this.setOptions(options)-这个函数是被Options类定义的-时,这些选项参数将被自动设置为事件。在这里我创建了一个onShow事件,在实际的类中,我写了onNext,onPrev,onSlideClick。注意你也能用addEvent来加载这些事件方法。
//在下面这个事件没有做任何事情,但是我们仍旧要执行它。
//我们为这个onShow赋了一个空函数,当它执行的时候什么事情也不做,基本等于把这个onShow赋值为function(){}。setOptions方法能找到所有的以“on”开头的选项参数,只要这些是函数,那么就会把它加入到类的事件中去。同时它也会把这些符合条件的选项参数移除掉,这个类的实体对象将没有this.options.onShow这个值。正因为如此,常见的做法是注释掉onShow那行(因为它只是一个空的功能,反正会被删除) 。注释行被保留是因为这样很容易阅读代码并了解选项可用,而不用先去看类的fireEvent调用。注释行在压缩后会被删除(另一种常见的做法) 。
//onShow: $empty
},
initialize: function(options){
this.setOptions(options);
this.addSlides(this.options.slides);
if(this.slides.length) this.showSlide(this.options.startIndex);
},
slides: [],
addSlides: function(slides){
$$(slides).each(function(slide){
this.slides.include($(slide));
slide.addEvent('click', this.cycleForward.bind(this));
}, this);
},
addSlide: function(slide){
this.addSlides($splat($(slide)));
},
cycleForward: function(){
if($chk(this.now) && this.now < this.slides.length-1) this.showSlide(this.now+1);
else if ((this.now) && this.options.wrap) this.showSlide(0);
else if(!$defined(this.now)) this.showSlide(this.options.startIndex);
},
cycleBack: function(){
if(this.now > 0) this.showSlide(this.now-1);
else if(this.options.wrap) this.showSlide(this.slides.length-1);
},
showSlide: function(iToShow){
//迄今为止我们的类还有个小小的问题,当我们渐弱前一张图片然后渐强后一张图片时会有延迟,我们注意到用户可能点击到两张图片的任意之一。
//有两种方法来解决这个问题,第一种是使用Chain类来累积这些循环请求,如果用户在我们显示幻灯片的过程中点击,我们把这个新的求求增加到链中,当当前变迁请求完成,我们顺序调用下一个。我在很多地方解决过这个问题(Fx.Reveal, IconMenu等等),但是我在这个教程中不打算介绍的那么复杂。
//另一种方法是在变迁的过程中忽略新的请求。这意味着如果我们在显示幻灯片的过程中用户点击了,那么我们什么也不做。因为这个解决方案不那么复杂,在这里我将使用这种方法。
//这意味着我们在变迁过程中时必须设置一个标记(this.fading = true),然后在变迁结束后设置为false。下面一行决定了任何一个在变迁过程中发生的幻灯片显示请求都会被忽略。
if (this.fading) return;
var now = this.now;
var currentSlide = this.slides[now];
var slide = this.slides[iToShow];
var fadeIn = function (s){
this.fading = true;
s.setStyles({
display:'block',
visibility: 'visible',
opacity: 0
});
//我们将不得不告诉类当触发我们在选项参数中添加的"onShow"事件。我希望这个事件将在图片渐强后被调用。为什么呢?因为其他代码可以在此预期用户能看到下一张幻灯片。加入有人想执行什么动作,我也可以在看到下一张幻灯片前添加一个事件。但在这个例子中,我们仅仅使用这个事件。
//我们使用Chain类方法来在一个效应(例如Fx.Tween)后执行一个方法。
s.get('tween').start('opacity', 1).chain(function(){
this.fading = false;
//我们继承的Event类提供给我们一个方法用来触发事件。可以传递一个参数用来指定要触发的事件名称(在此“on”被移除,例如“onShow”变成了“show”),任意多的参数传递给回调函数(多个参数需要在数组中)-这个参数可以省略掉,另外一个可省略的参数是delay (单位是毫秒)。在下面我传递了一个幻灯片DOM元素和索引。我得指出,这样在这篇文档中其他人就能知道参数是怎么传递给事件的。
this.fireEvent('onShow', [slide, iToShow]);
}.bind(this));
}.bind(this);
if(slide) {
if($chk(now) && now != iToShow){
this.fading = true;
currentSlide.get('tween').start('opacity', 0).chain(function(){
currentSlide.setStyle('display', 'none');
fadeIn(slide);
}.bind(this));
} else fadeIn(slide);
this.now = iToShow;
}
}
});
第九步:
var SimpleSlideShowDemo = new Class({
Implements: [Options, Events],
options: {
slides: [],
startIndex: 0,
wrap: true
//onShow: $empty
},
initialize: function(options){
this.setOptions(options)
this.addSlides(this.options.slides);
if(this.slides.length) this.showSlide(this.options.startIndex);
},
slides: [],
addSlides: function(slides){
$$(slides).each(function(slide){
this.slides.include($(slide));
slide.addEvent('click', this.cycleForward.bind(this));
}, this);
},
addSlide: function(slide){
this.addSlides($splat($(slide)));
},
cycleForward: function(){
if($chk(this.now) && this.now < this.slides.length-1) this.showSlide(this.now+1);
else if ((this.now) && this.options.wrap) this.showSlide(0);
else if(!$defined(this.now)) this.showSlide(this.options.startIndex);
},
cycleBack: function(){
if(this.now > 0) this.showSlide(this.now-1);
else if(this.options.wrap) this.showSlide(this.slides.length-1);
},
showSlide: function(iToShow){
if (this.fading) return;
var now = this.now;
var currentSlide = this.slides[now];
var slide = this.slides[iToShow];
var fadeIn = function (s){
this.fading = true;
s.setStyles({
display:'block',
visibility: 'visible',
opacity: 0
});
s.get('tween').start('opacity', 1).chain(function(){
this.fading = false;
this.fireEvent('onShow', [slide, iToShow]);
}.bind(this));
}.bind(this);
if(slide) {
if($chk(now) && now != iToShow){
this.fading = true;
currentSlide.get('tween').start('opacity', 0).chain(function(){
currentSlide.setStyle('display', 'none');
fadeIn(slide);
}.bind(this));
} else fadeIn(slide);
this.now = iToShow;
}
}
});
//我们现在已经有了一个完整的能工作的幻灯片类了,但是现在我们尤其想要将图片做成幻灯片效果。我们希望能将一组图片链接做成幻灯片效果。不用复制上面的代码,我们能通过继承它来增加更多功能。
//显而易见,这样确实不错,这有助于你走出自己的想法来想这个问题。尝试并且使类(函数和方法)做好一件事,然后构建更多的函数和类来增加更多的功能。这样做不仅仅减少了代码膨胀,也在遇到你没有预见的情况时对其他用户更加具有开放性。
//这里我创建了一个新的Class名字叫做SimpleImageSlideShowDemo.这个新的类使用了我们以上所写的类作为父类。我们使用Class.extend来继承SimpleSlideShowDemo.
var SimpleImageSlideShowDemo = new Class({
Extends: SimpleSlideShowDemo,
//在这里声明的选项参数和父类声明的选项参数会和谐共存,所以在此附加这些选项参数,这个类也有在父类SimpleSlideShowDemo中定义的选项参数。
options: {
imgUrls: [],
container: false
},
//我们在此必须要小心,因为我们正在使用父类中定义的命名空间,这将覆盖父类中的方法(initialize).
//我们通过调用this.parent()能执行此方法在父类中的功能。这能放在我们的方法中的任何地方。在这里,我希望在我的代码钱首先执行父类的initialize方法,所以我首先调用了它。同时注意,我没有调用this.setOptions(options),因为这已经在父类的方法中执行过了。
//当你调用this.parent()你需要传递给它所期望的参数。比如我们的initialize方法有多个参数,但是父类的initialize方法仅期望1个参数,那么我们必须传递给它合适的参数。
initialize: function(options){
this.parent(options);
this.container = $(this.options.container);
if(!this.container) return;
this.options.imgUrls.each(this.addImg.bind(this));
//在这个方法的父类版本中我们已经告诉它显示第一个幻灯片(在options中定义的startIndex)。我们不得不在此再做一次因为可能用户没有传入任何DOM中存在的图片或者幻灯片而是仅仅传入了个url数组。因而,当我们执行this.parent(options)的时候父类方法没有任何幻灯片。我们在上面的代码中添加所有图片后,我们仍需要显示一个。
this.showSlide(this.options.startIndex);
},
addImg: function(url){
var img = new Element('img', {
src: url,
styles: {
display: 'none'
}
}).inject($(this.options.container))
this.addSlide(img);
}
});
第十步:
window.addEvent('domready', function(){
//这里定义了一个我们的SimpleSlideShowDemo类的新的实体。我将搜索到的带有“slide”CSS类的div传入了构造函数,在这里有5个元素被传入。
new SimpleSlideShowDemo({
slides: $$('div.slide')
});
//这里是一个SimpleImageSlideShowDemo实体,我传入了一些url和一个用来放置图片的容器(译注:由于原文的图片链接已经失效,所以我替换了原文的url)。
new SimpleImageSlideShowDemo({
imgUrls: [
//"http://download.com/i/dl/media/dlimage/10/87/78/108778_medium.jpeg",
//"http://download.com/i/dl/media/dlimage/10/87/79/108779_medium.jpeg",
//"http://download.com/i/dl/media/dlimage/10/87/81/108781_medium.jpeg"
"http://mat1.qq.com/ent/upday2009/0902261.jpg",
"http://mat1.qq.com/ent/upday2009/0902262.jpg",
"http://mat1.qq.com/ent/upday2009/0902263.jpg",
"http://mat1.qq.com/ent/upday2009/0902264.jpg"
],
container: $('imgContainer')
});
});
尝试翻译的一篇文章,作者真的是手把手的教学,原文很酷,不过在这里保留原文的样式比较困难,我们还是关注文章内容吧:D。
如果有什么不对的地方还请大家回复告诉我,转载请注明出处。
原文:How to write a Mootools Class
在这篇文章,你将和我一起一步一步的写一个类。在这个例子里的幻灯片类,也在我们的通用类库里面,那里有更多本文没有提到的功能。
第一步:
// 每次我的类几乎都有一个同样的开端,我们就从这里开始。这个类有options,events,和一个初始化的部分。options和events不是必须的,但是除非我不打算复用代码,否则这个类都有options和events。
// 即使你只打算写写自己用的代码,还是要在你的类中提供这些挂钩,这将使你代码更加可复用。
var SimpleSlideShowDemo = new Class({
Implements: [Options, Events],
options: {
},
initialize: function(options){
this.setOptions(options);
}
});
第二步:
// 好了,我们正在写一个幻灯片类,那么在这样一个类中首先应该开始做什么?当然要有滑动,不是吗?此外,我们也要给用户(即创建这个类的实例的人,不是用浏览器观看效果的人,那样的人我会称之为观众)选项用来指定幻灯片从哪里开始,如果幻灯片会回到起点还要能知道它何时到达终点。
// 这些选项设置了默认值,但是用户可以修改它们。这个功能是Options类提供的。当我们实现Options的接口时,我们也就给我们的类增加了Options(TODO)中的约定和功能。我们能执行options中的this.setOptions来使用户修改选项的默认值,但是如果用户仅仅指定了一部分选项值,那么剩下的选项默认值不变。
// 重要的一点是注意选项是可选的。如果你的类运行需要一些参数,那么你就应该传递这些参数给你的类地初始化函数。在这里应该加入必要的滑动,除非我想晚些再加入滑动,那意味着我将创建一个没有滑动的幻灯片,然后我可以在晚些时候添加滑动,这使得滑动是可选的。
var SimpleSlideShowDemo = new Class({
Implements: [Options, Events],
options: {
slides: [],
startIndex: 0,
wrap: true
},
initialize: function(options){
//接下来我们设置options.即使用户不会传递参数,setOptions也会被传入一个null或者undefined。你也可以传递比这个对象更多的东西,不过我们这儿用不着。
this.setOptions(options);
//现在我们将用户传递的幻灯片添加到幻灯片的数组中,如果用户什么也没有传进来,那么就是一个空的幻灯片的数组,这样什么事情也不会发生。
this.addSlides(this.options.slides);
//如果有很多的幻灯片,我们就要显示出options中设置的第一个幻灯片
if(this.slides.length) this.showSlide(this.options.startIndex);
},
//设置一个数组成员用来存储那些幻灯片。我可以直接设置成this.options.slides,但是在这个例子中我打算通过我在下一步写的方法来操作这些幻灯片。有了这个独立的方法,用户就可以任意添加幻灯片了。
slides: []
});
第三步:
var SimpleSlideShowDemo = new Class({
Implements: [Options, Events],
options: {
slides: [],
startIndex: 0,
wrap: true
},
initialize: function(options){
this.setOptions(options);
this.addSlides(this.options.slides);
if(this.slides.length) this.showSlide(this.options.startIndex);
},
slides: [],
//好了,这次我们传递给addSlides方法来添加dom元素数组作为幻灯片,我们用.each方法来循环这个数组,并且将每个数组元素加入到幻灯片数组中去,就是this.slides这个属性。我们使用数组的.include,因而我们不需要复制一个副本出来。
//注意这里我通过$$来运行这个数组,如果已经是个集合那么什么都不会发生,这仅仅是返回集合,但是又不仅仅是一个集合,这个方法添加了MooTool的函数到这些数组上面。这意味着用户也可以选择传递一个选择器。
//译注:比如传递#myElement或者#myElement a.myClass之类的CSS选择器
addSlides: function(slides){
//我们使用了Array.each来迭代了幻灯片数组。在这里有一个重要的概念需要理解:this标记符。在一个类里面,this是对这个类的实体的引用。例如,"var SomeClass = new Class({});" 是一个类。 然后 "var instanceOfSomeClass = new SomeClass();" 创建了一个这个类的实体.
//我们使用this关键字来引用这个实体中的成员。this不是对类的引用,而是对这个实体的引用。
//在一个函数中,this引用的是这个函数,除非这个函数是一个类的方法(就像我们这个类的例子一样)。所以使用Array.each,你传递了两个参数,第一个是你希望在数组的每个元素上执行的函数,而另一个则是你想绑定this的这个对象。
//你需要这个绑定,因为你想执行的函数有this绑定到函数自身,而不是你的类。你仅仅需要指定这个绑定当你在这个函数中引用this的时候。如果你不打算引用你的类中的方法和属性,那么你不需要绑定任何东西(绑定了也不会有什么损害)。更多关于绑定(TODO)。
$$(slides).each(function(slide){
//我使用.include来将当前幻灯片加入到对象的slides属性,这将仅仅添加slides数组中原来不存在的幻灯片。
this.slides.include($(slide));
//在这里我们指定了绑定到类的this。
}, this);
},
//提供一个添加单个幻灯片的方法,它调用了上面的.addSlides来将这个幻灯片用$splat包装成数组后添加进去。
addSlide: function(slide){
this.addSlides($splat($(slide)));
}
});
第四步:
var SimpleSlideShowDemo = new Class({
Implements: [Options, Events],
options: {
slides: [],
startIndex: 0,
wrap: true
},
initialize: function(options){
this.setOptions(options);
this.addSlides(this.options.slides);
if(this.slides.length) this.showSlide(this.options.startIndex);
},
slides: [],
addSlides: function(slides){
$$(slides).each(function(slide){
this.slides.include($(slide));
//现在用户可以有办法来完成这个幻灯片显示。在这个类的真实例子(SimpleSlideShow(TODO) in CNET's common library)中,我让用户指定了dom元素数组用来向前和向后。在这个例子中,让我们来添加一个行为到幻灯片上,如果用户点击了幻灯片,我们循环向前。
//Element.addEvent(TODO)添加了用于这个元素的函数的事件句柄,但是默认this绑定到这个元素的函数,这样在这个Element.addEvent函数中,this意味着这个元素,可是我们需要它是这个我们正在编辑的类,所以当我们添加事件的时候不得不绑定我们的类到这个函数上。
//注意,不像Array.each,这里我们必须使用.bind方法,因为addEvent不接收第三个参数用来绑定。
slide.addEvent('click', this.cycleForward.bind(this));
}, this);
},
addSlide: function(slide){
this.addSlides($splat($(slide)));
}
});
第五步:
var SimpleSlideShowDemo = new Class({
Implements: [Options, Events],
options: {
slides: [],
startIndex: 0,
wrap: true
},
initialize: function(options){
this.setOptions(options);
this.addSlides(this.options.slides);
if(this.slides.length) this.showSlide(this.options.startIndex);
},
slides: [],
addSlides: function(slides){
$$(slides).each(function(slide){
this.slides.include($(slide));
slide.addEvent('click', this.cycleForward.bind(this));
}, this);
},
addSlide: function(slide){
this.addSlides($splat($(slide)));
},
//循环向前和循环向后。在这个短小的例子中,我实际没有定义一个方法来让用户可以循环向后,仅仅能循环向前,但是在真实的应用中我使用在options中的next/previous链接来让用户同时能做这两件事情。
cycleForward: function(){
//如果this.now已经被定义(当前可访问的幻灯片索引),那么我能继续增加,就可以显示下一张幻灯片。
if($chk(this.now) && this.now < this.slides.length-1) this.showSlide(this.now+1);
//如果this.now 已经被定义并且既不是null也不是0(最后一个),那么根据wrap,可以返回到0显示第一个幻灯片。
else if ((this.now) && this.options.wrap) this.showSlide(0);
//如果,理论上,this.now并没有定义因为我们以前从来没有显示过幻灯片,那么显示第一个参数设定。
else if(!$defined(this.now)) this.showSlide(this.options.startIndex);
//这个类没有用到下面的函数,但是还是将循环向后的函数写在这里了。这里假想循环向后被调用了最少一次(在你初始化这个类后调用),因此this.now有一个值。在真实的类中(SimpleSlideShow)我让用户传入那些dom元素的引用来向前和向后。
},
cycleBack: function(){
if(this.now > 0) this.showSlide(this.now-1);
else if(this.options.wrap) this.showSlide(this.slides.length-1);
}
});
第六步:
var SimpleSlideShowDemo = new Class({
Implements: [Options, Events],
options: {
slides: [],
startIndex: 0,
wrap: true
},
initialize: function(options){
this.setOptions(options);
this.addSlides(this.options.slides);
if(this.slides.length) this.showSlide(this.options.startIndex);
},
slides: [],
addSlides: function(slides){
$$(slides).each(function(slide){
this.slides.include($(slide));
slide.addEvent('click', this.cycleForward.bind(this));
}, this);
},
addSlide: function(slide){
this.addSlides($splat($(slide)));
},
cycleForward: function(){
if($chk(this.now) && this.now < this.slides.length-1) this.showSlide(this.now+1);
else if ((this.now) && this.options.wrap) this.showSlide(0);
else if(!$defined(this.now)) this.showSlide(this.options.startIndex);
},
cycleBack: function(){
if(this.now > 0) this.showSlide(this.now-1);
else if(this.options.wrap) this.showSlide(this.slides.length-1);
},
//显示一个幻灯片意味着我们设置css属性"display"为"block"给这个幻灯片元素,并且将其他的幻灯片元素设置为"none"。
showSlide: function(iToShow){
//如果传入的幻灯片索引在我们的可用的幻灯片中就显示这张幻灯片。假如这是第一次尝试显示幻灯片,this.now并没有被定义,那么仅仅显示当前幻灯片,如果已经被定义了,就需要隐藏当前的幻灯片(如果我们没有确定就直接执行.setStyle在未定义的变量上,那么就会出错)。
if(this.slides[iToShow]) {
if(this.slides[this.now]) this.slides[this.now].setStyle('display','none');
this.slides[iToShow].setStyle('display','block');
this.now = iToShow;
}
}
}
});
第七步:
var SimpleSlideShowDemo = new Class({
Implements: [Options, Events],
options: {
slides: [],
startIndex: 0,
wrap: true
},
initialize: function(options){
this.setOptions(options);
this.addSlides(this.options.slides);
if(this.slides.length) this.showSlide(this.options.startIndex);
},
slides: [],
addSlides: function(slides){
$$(slides).each(function(slide){
this.slides.include($(slide));
slide.addEvent('click', this.cycleForward.bind(this));
}, this);
},
addSlide: function(slide){
this.addSlides($splat($(slide)));
},
cycleForward: function(){
if($chk(this.now) && this.now < this.slides.length-1) this.showSlide(this.now+1);
else if ((this.now) && this.options.wrap) this.showSlide(0);
else if(!$defined(this.now)) this.showSlide(this.options.startIndex);
},
cycleBack: function(){
if(this.now > 0) this.showSlide(this.now-1);
else if(this.options.wrap) this.showSlide(this.slides.length-1);
},
//当然,仅仅显示幻灯片并不好玩。我们希望能够让这些幻灯片渐变。有几种方法能做到这种变迁,让我们来试着让它简单些。我们希望当前的幻灯片渐弱直到消失,还希望下一张幻灯片能够渐强显示。
showSlide: function(iToShow){
//以下几行你能看到我们设置了this.now来等于我们当前正在显示的幻灯片索引(iToShow)。这样我们的类就随时能够知道当前显示的幻灯片索引。
//但是我们还有个新的好主意:变迁。因为这个变迁需要花些时间,我们不能引用this.now,因为它在我们的变迁花费时间的时候已经变化了。在一秒中发生了很多,这就是为什么我定义了变量now,在this.now被污染的时候存储它。
var now = this.now;
var currentSlide = this.slides[now];
var slide = this.slides[iToShow];
//这里是一个闭包的例子。这个fadein方法,可以引用已经被声明的值,但是我们仅仅想在当前的幻灯片渐弱完成后再对下一张幻灯片渐强。在执行的时候,这里声明的函数能获得整个方法中的变量。所以即使函数在上一张幻灯片渐弱后被调用,它仍能调用这里定义的值。
//整个函数用来改变元素的样式为display:block,但是隐藏(透明),然后从透明到不透明渐变。
//注意这个函数之前没有被声明,其实我可以在下面的代码中匿名执行它。把这个函数声明在这里的原因是需要调用两次并且我不想重复代码。
var fadeIn = function (s){
s.setStyles({
display:'block',
visibility: 'visible',
opacity: 0
});
s.get('tween').start('opacity', 1);
}.bind(this);
//注意在上面我绑定this到这个函数来确保任何对此的调用都是在调用这个类的对象。
if(slide) {
//第一次这个方法被调用时还没有显示幻灯片,所以不需要渐弱效果。如果不是这种情况(不是第一次调用),我们将需要对当前幻灯片渐弱后再对下一张幻灯片渐强。
if($chk(now) && now != iToShow){
//我们渐弱当前的幻灯片后,使用Chain(TODO),执行我们的函数到渐强下一张幻灯片。
currentSlide.get('tween').start('opacity', 0).chain(function(){
//注意当前幻灯片已经被隐藏,我们设置它display:none。我们的透明度刚刚被设置成透明,但是在我们显示下一张幻灯片前,需要将当前的幻灯片从页面流中清出去。
currentSlide.setStyle('display', 'none');
//现在我们用上面的fadeIn函数来渐强下一张幻灯片。
fadeIn(slide);
//注意这里我们使用了.bind(this),因为在这个函数(我们传递给.chain的)里面,我们需要调用这个类的实体对象。一些方法让你传递一个绑定的对象,像Array.each或者Function.apply(TODO)。当这无法做到,你必须用.bind(this)来达到这个效果。
//译注:如果不.bind(this),currentSlide被作为this使用。
}.bind(this));
//如果变量now 是undefined或者指定的幻灯片已经显示,直接立刻渐强幻灯片,如果幻灯片已经显示,这个渐强函数什么也不会改变。
} else fadeIn(slide);
//现在我们保存this.now为当前正显示的幻灯片。因为我们在上面的函数链中调用了now(换句话说,我们在本来显示的幻灯片隐藏后调用了now),我们不能引用this.now,因为下一行将在函数链的代码执行完渐弱后立刻执行
this.now = iToShow;
}
}
});
第八步:
var SimpleSlideShowDemo = new Class({
Implements: [Options, Events],
options: {
slides: [],
startIndex: 0,
wrap: true
//Event(TODO)能使你添加自定义事件到你的类上。这真是非常强大并且能让你的代码更加的可复用。我推荐在你的代码中增加这些事件,onShow, onHide, onComplete, onStart, onError等等。这能让其他人(也包括你自己)约束这个类的行为在非侵扰性的方式上。你不需要有太多的事件。
//因为我们在这个类上实现了Options,所以我们继承了那个类的一些关于事件的方法。如果你有哪个选项参数是用“on”开头,当你调用this.setOptions(options)-这个函数是被Options类定义的-时,这些选项参数将被自动设置为事件。在这里我创建了一个onShow事件,在实际的类中,我写了onNext,onPrev,onSlideClick。注意你也能用addEvent来加载这些事件方法。
//在下面这个事件没有做任何事情,但是我们仍旧要执行它。
//我们为这个onShow赋了一个空函数,当它执行的时候什么事情也不做,基本等于把这个onShow赋值为function(){}。setOptions方法能找到所有的以“on”开头的选项参数,只要这些是函数,那么就会把它加入到类的事件中去。同时它也会把这些符合条件的选项参数移除掉,这个类的实体对象将没有this.options.onShow这个值。正因为如此,常见的做法是注释掉onShow那行(因为它只是一个空的功能,反正会被删除) 。注释行被保留是因为这样很容易阅读代码并了解选项可用,而不用先去看类的fireEvent调用。注释行在压缩后会被删除(另一种常见的做法) 。
//onShow: $empty
},
initialize: function(options){
this.setOptions(options);
this.addSlides(this.options.slides);
if(this.slides.length) this.showSlide(this.options.startIndex);
},
slides: [],
addSlides: function(slides){
$$(slides).each(function(slide){
this.slides.include($(slide));
slide.addEvent('click', this.cycleForward.bind(this));
}, this);
},
addSlide: function(slide){
this.addSlides($splat($(slide)));
},
cycleForward: function(){
if($chk(this.now) && this.now < this.slides.length-1) this.showSlide(this.now+1);
else if ((this.now) && this.options.wrap) this.showSlide(0);
else if(!$defined(this.now)) this.showSlide(this.options.startIndex);
},
cycleBack: function(){
if(this.now > 0) this.showSlide(this.now-1);
else if(this.options.wrap) this.showSlide(this.slides.length-1);
},
showSlide: function(iToShow){
//迄今为止我们的类还有个小小的问题,当我们渐弱前一张图片然后渐强后一张图片时会有延迟,我们注意到用户可能点击到两张图片的任意之一。
//有两种方法来解决这个问题,第一种是使用Chain类来累积这些循环请求,如果用户在我们显示幻灯片的过程中点击,我们把这个新的求求增加到链中,当当前变迁请求完成,我们顺序调用下一个。我在很多地方解决过这个问题(Fx.Reveal, IconMenu等等),但是我在这个教程中不打算介绍的那么复杂。
//另一种方法是在变迁的过程中忽略新的请求。这意味着如果我们在显示幻灯片的过程中用户点击了,那么我们什么也不做。因为这个解决方案不那么复杂,在这里我将使用这种方法。
//这意味着我们在变迁过程中时必须设置一个标记(this.fading = true),然后在变迁结束后设置为false。下面一行决定了任何一个在变迁过程中发生的幻灯片显示请求都会被忽略。
if (this.fading) return;
var now = this.now;
var currentSlide = this.slides[now];
var slide = this.slides[iToShow];
var fadeIn = function (s){
this.fading = true;
s.setStyles({
display:'block',
visibility: 'visible',
opacity: 0
});
//我们将不得不告诉类当触发我们在选项参数中添加的"onShow"事件。我希望这个事件将在图片渐强后被调用。为什么呢?因为其他代码可以在此预期用户能看到下一张幻灯片。加入有人想执行什么动作,我也可以在看到下一张幻灯片前添加一个事件。但在这个例子中,我们仅仅使用这个事件。
//我们使用Chain类方法来在一个效应(例如Fx.Tween)后执行一个方法。
s.get('tween').start('opacity', 1).chain(function(){
this.fading = false;
//我们继承的Event类提供给我们一个方法用来触发事件。可以传递一个参数用来指定要触发的事件名称(在此“on”被移除,例如“onShow”变成了“show”),任意多的参数传递给回调函数(多个参数需要在数组中)-这个参数可以省略掉,另外一个可省略的参数是delay (单位是毫秒)。在下面我传递了一个幻灯片DOM元素和索引。我得指出,这样在这篇文档中其他人就能知道参数是怎么传递给事件的。
this.fireEvent('onShow', [slide, iToShow]);
}.bind(this));
}.bind(this);
if(slide) {
if($chk(now) && now != iToShow){
this.fading = true;
currentSlide.get('tween').start('opacity', 0).chain(function(){
currentSlide.setStyle('display', 'none');
fadeIn(slide);
}.bind(this));
} else fadeIn(slide);
this.now = iToShow;
}
}
});
第九步:
var SimpleSlideShowDemo = new Class({
Implements: [Options, Events],
options: {
slides: [],
startIndex: 0,
wrap: true
//onShow: $empty
},
initialize: function(options){
this.setOptions(options)
this.addSlides(this.options.slides);
if(this.slides.length) this.showSlide(this.options.startIndex);
},
slides: [],
addSlides: function(slides){
$$(slides).each(function(slide){
this.slides.include($(slide));
slide.addEvent('click', this.cycleForward.bind(this));
}, this);
},
addSlide: function(slide){
this.addSlides($splat($(slide)));
},
cycleForward: function(){
if($chk(this.now) && this.now < this.slides.length-1) this.showSlide(this.now+1);
else if ((this.now) && this.options.wrap) this.showSlide(0);
else if(!$defined(this.now)) this.showSlide(this.options.startIndex);
},
cycleBack: function(){
if(this.now > 0) this.showSlide(this.now-1);
else if(this.options.wrap) this.showSlide(this.slides.length-1);
},
showSlide: function(iToShow){
if (this.fading) return;
var now = this.now;
var currentSlide = this.slides[now];
var slide = this.slides[iToShow];
var fadeIn = function (s){
this.fading = true;
s.setStyles({
display:'block',
visibility: 'visible',
opacity: 0
});
s.get('tween').start('opacity', 1).chain(function(){
this.fading = false;
this.fireEvent('onShow', [slide, iToShow]);
}.bind(this));
}.bind(this);
if(slide) {
if($chk(now) && now != iToShow){
this.fading = true;
currentSlide.get('tween').start('opacity', 0).chain(function(){
currentSlide.setStyle('display', 'none');
fadeIn(slide);
}.bind(this));
} else fadeIn(slide);
this.now = iToShow;
}
}
});
//我们现在已经有了一个完整的能工作的幻灯片类了,但是现在我们尤其想要将图片做成幻灯片效果。我们希望能将一组图片链接做成幻灯片效果。不用复制上面的代码,我们能通过继承它来增加更多功能。
//显而易见,这样确实不错,这有助于你走出自己的想法来想这个问题。尝试并且使类(函数和方法)做好一件事,然后构建更多的函数和类来增加更多的功能。这样做不仅仅减少了代码膨胀,也在遇到你没有预见的情况时对其他用户更加具有开放性。
//这里我创建了一个新的Class名字叫做SimpleImageSlideShowDemo.这个新的类使用了我们以上所写的类作为父类。我们使用Class.extend来继承SimpleSlideShowDemo.
var SimpleImageSlideShowDemo = new Class({
Extends: SimpleSlideShowDemo,
//在这里声明的选项参数和父类声明的选项参数会和谐共存,所以在此附加这些选项参数,这个类也有在父类SimpleSlideShowDemo中定义的选项参数。
options: {
imgUrls: [],
container: false
},
//我们在此必须要小心,因为我们正在使用父类中定义的命名空间,这将覆盖父类中的方法(initialize).
//我们通过调用this.parent()能执行此方法在父类中的功能。这能放在我们的方法中的任何地方。在这里,我希望在我的代码钱首先执行父类的initialize方法,所以我首先调用了它。同时注意,我没有调用this.setOptions(options),因为这已经在父类的方法中执行过了。
//当你调用this.parent()你需要传递给它所期望的参数。比如我们的initialize方法有多个参数,但是父类的initialize方法仅期望1个参数,那么我们必须传递给它合适的参数。
initialize: function(options){
this.parent(options);
this.container = $(this.options.container);
if(!this.container) return;
this.options.imgUrls.each(this.addImg.bind(this));
//在这个方法的父类版本中我们已经告诉它显示第一个幻灯片(在options中定义的startIndex)。我们不得不在此再做一次因为可能用户没有传入任何DOM中存在的图片或者幻灯片而是仅仅传入了个url数组。因而,当我们执行this.parent(options)的时候父类方法没有任何幻灯片。我们在上面的代码中添加所有图片后,我们仍需要显示一个。
this.showSlide(this.options.startIndex);
},
addImg: function(url){
var img = new Element('img', {
src: url,
styles: {
display: 'none'
}
}).inject($(this.options.container))
this.addSlide(img);
}
});
第十步:
window.addEvent('domready', function(){
//这里定义了一个我们的SimpleSlideShowDemo类的新的实体。我将搜索到的带有“slide”CSS类的div传入了构造函数,在这里有5个元素被传入。
new SimpleSlideShowDemo({
slides: $$('div.slide')
});
//这里是一个SimpleImageSlideShowDemo实体,我传入了一些url和一个用来放置图片的容器(译注:由于原文的图片链接已经失效,所以我替换了原文的url)。
new SimpleImageSlideShowDemo({
imgUrls: [
//"http://download.com/i/dl/media/dlimage/10/87/78/108778_medium.jpeg",
//"http://download.com/i/dl/media/dlimage/10/87/79/108779_medium.jpeg",
//"http://download.com/i/dl/media/dlimage/10/87/81/108781_medium.jpeg"
"http://mat1.qq.com/ent/upday2009/0902261.jpg",
"http://mat1.qq.com/ent/upday2009/0902262.jpg",
"http://mat1.qq.com/ent/upday2009/0902263.jpg",
"http://mat1.qq.com/ent/upday2009/0902264.jpg"
],
container: $('imgContainer')
});
});