script.aculo.usを読み解く

第12回dragdrop.js (中編)

3回に分けてお送りしているdragdrop.jsの解説の、第2回目です。次回でこの連載も最終回となります。

Draggable

ドラッグ可能要素のクラスです。機能としては、ドラッグ操作にエフェクトをかける、ドラッグにあわせて画面をスクロールする、ドラッグ位置をスナップするなどがあります。ここでクラスに登場するプロパティの意味を解説します。

element
ドラッグ可能になる要素です。
handle
ドラッグの⁠把手⁠になる要素です。デフォルトではelementの要素と同一です。ドラッグ開始のためのmousedownイベントハンドラはこの要素を監視します。
dragging
現在ドラッグ中かどうかのフラグです。
delta
ドラッグの出発地点です。[x,y]の配列です。
offset
ドラッグを開始したときの、マウスポインタの位置と要素の位置のズレの量です。
_clone
options.ghostingが有効のときに出発地点に表示される、要素のクローンです。
_isScrollChild
element要素が、options.scroll要素の子要素かどうかです。
originalScrollLeft、originalScrollTop
ドラッグを開始したときのoptions.scroll要素のスクロール位置です。
options.handle
ドラッグの⁠把手⁠になる要素を指定します。指定方法は、DOM idか、element要素の子要素が含むクラス名です。デフォルトはドラッグ可能要素と同一です。
options.starteffect
ドラッグを開始したときのフックです。デフォルトではEffect.Opacityで要素をフェードアウトします。
options.change
ドラッグ中に位置が変化するたびに呼ぶフックです。
options.reverteffect
ドラッグをキャンセルしたとき、または、ドロップが失敗したときのフックです。デフォルトではEffect.Moveで要素が元の位置に戻る様子をアニメーションで表現します。
options.endeffect
ドラッグを終了したときのフックです。デフォルトではEffect.Opacityで要素をフェードインします。
options.onDropped
ドロップした直後に呼ぶフックです。
options.zindex
ドラッグ中の要素のCSSのzindexプロパティの値を指定します。デフォルトは1000です。
options.revert
ドラッグを終了したときのフックを呼ぶかどうかです。デフォルトではfalseです。真偽値を与えるほかに、関数を与えることもできます。この関数は、ドラッグの終了時に、ドラッグした要素を引数に呼ばれるので、そこで真偽値を返すようにします。
options.quiet
ドラッグ中に、ドロップ先の変化の表示を一切しないオプションです。デフォルトではfalseです。
options.scroll
ドラッグにあわせてスクロールする要素を指定します。デフォルトはwindowで、ブラウザのスクロールが動きます。
options.scrollSensitivity
マウスポインタと、options.scroll要素の短形領域の枠がこの値以下に近づくと、スクロールを始めます。デフォルトは20pxです。大きければ大きいほどスクロールが始まりやすくなります。
options.scrollSpeed
スクロールの速度です。デフォルトは15px毎秒です。
options.snap
スナップするかどうか、するならばその幅を指定します。デフォルトではfalseです。3つの指定方法があります。配列[x,y]か、数値aか(配列[a,a]を与えるのと同じです⁠⁠、関数(引数にx座標,y座標をとり、配列[x,y]を返すようにします)です。
options.delay
Draggablesのactivate関数で、処理を指定ミリ秒間遅延するためのオプションです。デフォルトは0です。
options.ghosting
ドラッグ中に元の場所に要素のクローンを表示するかどうかです。デフォルトはfalseです。

それではコードを見ていきましょう。

0227:var Draggable = Class.create({
0228:  initialize: function(element) {
0229:    var defaults = {
0230:      handle: false,
0231:      reverteffect: function(element, top_offset, left_offset) {
0232:        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
0233:        new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
0234:          queue: {scope:'_draggable', position:'end'}
0235:        });
0236:      },
0237:      endeffect: function(element) {
0238:        var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
0239:        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, 
0240:          queue: {scope:'_draggable', position:'end'},
0241:          afterFinish: function(){ 
0242:            Draggable._dragging[element] = false 
0243:          }
0244:        }); 
0245:      },
0246:      zindex: 1000,
0247:      revert: false,
0248:      quiet: false,
0249:      scroll: false,
0250:      scrollSensitivity: 20,
0251:      scrollSpeed: 15,
0252:      snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }
0253:      delay: 0
0254:    };
0255:    
0256:    if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
0257:      Object.extend(defaults, {
0258:        starteffect: function(element) {
0259:          element._opacity = Element.getOpacity(element);
0260:          Draggable._dragging[element] = true;
0261:          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 
0262:        }
0263:      });
0264:    
0265:    var options = Object.extend(defaults, arguments[1] || { });
0266:
0267:    this.element = $(element);
0268:    
0269:    if(options.handle && Object.isString(options.handle))
0270:      this.handle = this.element.down('.'+options.handle, 0);
0271:    
0272:    if(!this.handle) this.handle = $(options.handle);
0273:    if(!this.handle) this.handle = this.element;
0274:    
0275:    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
0276:      options.scroll = $(options.scroll);
0277:      this._isScrollChild = Element.childOf(this.element, options.scroll);
0278:    }
0279:
0280:    Element.makePositioned(this.element); // fix IE    
0281:
0282:    this.options  = options;
0283:    this.dragging = false;   
0284:
0285:    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
0286:    Event.observe(this.handle, "mousedown", this.eventMouseDown);
0287:    
0288:    Draggables.register(this);
0289:  },
0290:  

227~290行目のinitializeは、インスタンスの初期化をする関数です。種々のプロパティは上述の通りです。

256~263行目で、デフォルトのstarteffectのフェードアウトは、デフォルトのendeffectのフェードインと一対になる必要があるので、ユーザが両方ともデフォルトから変更していないことを確かめてから、starteffectを追加しています。

269行目で、まずoptions.handleに、element要素の子要素が含むクラス名を与えた場合を扱います。

その場合、270行目で、Element.downメソッドで該当する要素を取得します。

272行目で、次に、options.handleにDOM idを与えた場合を扱います。

273行目で、何も与えていない場合は、element要素になります。

275~278行目で、options.scrollに、DOM idが与えられた場合は、$関数を適用し、さらに、それがelementの親要素かどうかを_isScrollChildに保存します。

280行目で、Element.makePositionedで、要素の移動に備えます。

286行目で、ハンドル(把手)の要素にmousedownイベントハンドラを設定します。

288行目で、Draggablesクラスに登録します。

0291:  destroy: function() {
0292:    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
0293:    Draggables.unregister(this);
0294:  },
0295:  

291~295行目のdestroyは、ドラッグ機能を解除する関数です。

292行目で、イベントハンドラを解除します。

293行目で、Draggablesクラスから削除します。

0296:  currentDelta: function() {
0297:    return([
0298:      parseInt(Element.getStyle(this.element,'left') || '0'),
0299:      parseInt(Element.getStyle(this.element,'top') || '0')]);
0300:  },
0301:  

296~301行目のcurrentDeltaは、現在のelement要素のCSSのleft、topプロパティの値を[x,y]の配列のかたちで返す関数です。

0302:  initDrag: function(event) {
0303:    if(!Object.isUndefined(Draggable._dragging[this.element]) &&
0304:      Draggable._dragging[this.element]) return;
0305:    if(Event.isLeftClick(event)) {    
0306:      // abort on form elements, fixes a Firefox issue
0307:      var src = Event.element(event);
0308:      if((tag_name = src.tagName.toUpperCase()) && (
0309:        tag_name=='INPUT' ||
0310:        tag_name=='SELECT' ||
0311:        tag_name=='OPTION' ||
0312:        tag_name=='BUTTON' ||
0313:        tag_name=='TEXTAREA')) return;
0314:        
0315:      var pointer = [Event.pointerX(event), Event.pointerY(event)];
0316:      var pos     = Position.cumulativeOffset(this.element);
0317:      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
0318:      
0319:      Draggables.activate(this);
0320:      Event.stop(event);
0321:    }
0322:  },
0323:  

302~323行目のinitDragは、handle要素のmousedownイベントハンドラで、ドラッグの開始直前に呼ばれる関数です。

303行目で、Draggable._draggingを調べて、本当にドラッグの直前かどうかを確かめます。

Firefoxでは、フォームを構成する要素はドラッグ&ドロップに対応しません。306~313行目で、要素のタグ名を調べて、input、select、option、button、textareaの要素であれば処理を中止します。

317行目で、offsetは、ドラッグを開始したときの、マウスポインタの位置と要素の位置のズレです。

319行目で、Draggables.activateで、この要素を現在ドラッグ中とします。

0324:  startDrag: function(event) {
0325:    this.dragging = true;
0326:    if(!this.delta)
0327:      this.delta = this.currentDelta();
0328:    
0329:    if(this.options.zindex) {
0330:      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
0331:      this.element.style.zIndex = this.options.zindex;
0332:    }
0333:    
0334:    if(this.options.ghosting) {
0335:      this._clone = this.element.cloneNode(true);
0336:      this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
0337:      if (!this.element._originallyAbsolute)
0338:        Position.absolutize(this.element);
0339:      this.element.parentNode.insertBefore(this._clone, this.element);
0340:    }
0341:    
0342:    if(this.options.scroll) {
0343:      if (this.options.scroll == window) {
0344:        var where = this._getWindowScroll(this.options.scroll);
0345:        this.originalScrollLeft = where.left;
0346:        this.originalScrollTop = where.top;
0347:      } else {
0348:        this.originalScrollLeft = this.options.scroll.scrollLeft;
0349:        this.originalScrollTop = this.options.scroll.scrollTop;
0350:      }
0351:    }
0352:    
0353:    Draggables.notify('onStart', this, event);
0354:        
0355:    if(this.options.starteffect) this.options.starteffect(this.element);
0356:  },
0357:  

324~357行目のstartDragは、ドラッグの開始時に呼ばれる関数です。はじめてupdateDragが呼ばれたときに呼ばれます。

325行目で、ドラッグ中であることを示すdraggingフラグをたてます。

326行目で、deltaは、ドラッグの出発地点を表します。この要素にとってドラッグでの移動が初めてであれば、ここで現在の位置を出発地点とします。

329~333行目で、options.zindexの設定がある場合は、originalZに要素のCSSのz-indexプロパティの元の値を保存してから、値を書きかえます。

334~341行目で、options.ghostingの設定がある場合は、element要素のクローンを作り、それを要素のDOMツリーの直前に挿入します。その際に表示が乱れないよう、Position.absolutizeで要素をフローから外します。このとき、要素のCSSのpositionプロパティの元の値を_originallyAbsoluteに保存しておきます。

342~352行目で、originalScrollTop、同Leftを設定します。options.scrollがwindowであればページのスクロールの位置を、そうでなければoptions.scrollの要素のスクロールの位置とします。

353行目で、Draggables.notifyで、'onStart'関係のフックを呼びます。

355行目で、options.starteffectフックがあれば呼びます。

0358:  updateDrag: function(event, pointer) {
0359:    if(!this.dragging) this.startDrag(event);
0360:    
0361:    if(!this.options.quiet){
0362:      Position.prepare();
0363:      Droppables.show(pointer, this.element);
0364:    }
0365:    
0366:    Draggables.notify('onDrag', this, event);
0367:    
0368:    this.draw(pointer);
0369:    if(this.options.change) this.options.change(this);
0370:    
0371:    if(this.options.scroll) {
0372:      this.stopScrolling();
0373:      
0374:      var p;
0375:      if (this.options.scroll == window) {
0376:        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
0377:      } else {
0378:        p = Position.page(this.options.scroll);
0379:        p[0] += this.options.scroll.scrollLeft + Position.deltaX;
0380:        p[1] += this.options.scroll.scrollTop + Position.deltaY;
0381:        p.push(p[0]+this.options.scroll.offsetWidth);
0382:        p.push(p[1]+this.options.scroll.offsetHeight);
0383:      }
0384:      var speed = [0,0];
0385:      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
0386:      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
0387:      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
0388:      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
0389:      this.startScrolling(speed);
0390:    }
0391:    
0392:    // fix AppleWebKit rendering
0393:    if(Prototype.Browser.WebKit) window.scrollBy(0,0);
0394:    
0395:    Event.stop(event);
0396:  },
0397:  

358~397行目のupdateDragは、ドラッグ中にマウスポインタの位置が変化するたびに呼ばれる関数です。

359行目で、初めての呼び出しのとき、startDragを呼びます。

361~364行目で、options.quietの設定がある場合は、Droppables.showによるドロップ先の変化の表示を一切しません。

366行目で、Draggables.notifyで、'onDrag'関係のフックを呼びます。

368行目で、draw関数を呼んで、ドラッグ中の要素を描画します。

369行目で、options.changeフックがあれば呼びます。

371~388行目で、options.scrollの設定がある場合は、マウスポインタの位置の、options.scroll要素の表示領域からはみ出している分をスクロール量として求めます。

389行目で、startScrollingでスクロールを開始します。

393行目で、Safari系ブラウザには、再描画を促します。

0398:  finishDrag: function(event, success) {
0399:    this.dragging = false;
0400:    
0401:    if(this.options.quiet){
0402:      Position.prepare();
0403:      var pointer = [Event.pointerX(event), Event.pointerY(event)];
0404:      Droppables.show(pointer, this.element);
0405:    }
0406:
0407:    if(this.options.ghosting) {
0408:      if (!this.element._originallyAbsolute)
0409:        Position.relativize(this.element);
0410:      delete this.element._originallyAbsolute;
0411:      Element.remove(this._clone);
0412:      this._clone = null;
0413:    }
0414:
0415:    var dropped = false; 
0416:    if(success) { 
0417:      dropped = Droppables.fire(event, this.element); 
0418:      if (!dropped) dropped = false; 
0419:    }
0420:    if(dropped && this.options.onDropped) this.options.onDropped(this.element);
0421:    Draggables.notify('onEnd', this, event);
0422:
0423:    var revert = this.options.revert;
0424:    if(revert && Object.isFunction(revert)) revert = revert(this.element);
0425:    
0426:    var d = this.currentDelta();
0427:    if(revert && this.options.reverteffect) {
0428:      if (dropped == 0 || revert != 'failure')
0429:        this.options.reverteffect(this.element,
0430:          d[1]-this.delta[1], d[0]-this.delta[0]);
0431:    } else {
0432:      this.delta = d;
0433:    }
0434:
0435:    if(this.options.zindex)
0436:      this.element.style.zIndex = this.originalZ;
0437:
0438:    if(this.options.endeffect) 
0439:      this.options.endeffect(this.element);
0440:      
0441:    Draggables.deactivate(this);
0442:    Droppables.reset();
0443:  },
0444:  

398~444行目のfinishDragは、ドラッグの終了時の処理を行う関数です。この終了時には、ドラッグがESCキーでキャンセルされた場合も含みます。引数に、イベント情報と、キャンセルされずに成功したかの真偽値をとります。

399行目で、ドラッグ中であることを示すdraggingフラグを戻します。

401~405行目で、options.quietの設定がある場合は、Droppables.showによるドロップ先の変化の表示を一切しません。

407~413行目で、options.ghostingで要素のクローンを表示していた場合は、それを削除します。

408行目で、elementのCSSのpositionプロパティを元に戻します。

411行目で、_clone要素を削除します。

416~419行目で、ドラッグがESCキーでキャンセルされたのでない場合は、ドロップを試みます。

417行目で、Droppables.fireで、この座標、このドラッグ要素で、ドロップが成功するか調べます。

420行目で、ドロップが成功した場合、onDroppedフックがあれば呼びます。

421行目のDraggables.notifyで、'onEnd'関係のフックを呼びます。

423行目のoptions.revertは、ドラッグを終了したときのフックを呼ぶかどうかです。真偽値を与えるほかに、関数を与えることもできます。

424行目で、options.revertに関数が与えられた場合を処理します。

427~430行目で、revertが有効で、かつドロップが失敗した場合、options.reverteffectフックを呼びます。このフックには、ドラッグの出発地点からの移動量を計算して渡します。

431行目で、revertが無効なら、ドラッグの終了地点がここに定まるので、ドラッグの出発地点deltaを現在地点に更新します。

435行目で、options.zindexの設定がある場合は、要素のCSSのz-indexプロパティをoriginalZの情報で元に戻します。

438行目で、options.endeffectフックがあれば呼びます。

441行目のDraggables.deactivateで、現在ドラッグ中、としていた情報を元に戻します。

442行目のDroppables.resetで、ドロップ先の情報を元に戻します。

0445:  keyPress: function(event) {
0446:    if(event.keyCode!=Event.KEY_ESC) return;
0447:    this.finishDrag(event, false);
0448:    Event.stop(event);
0449:  },
0450:  

445~450行目のkeyPressは、ドラッグ中にESCキーを押すとドラッグをキャンセルする関数です。documentオブジェクトのkeypressイベントのハンドラから呼ばれます。

447行目で、finishDrag関数を、2番目の引数をfalseで呼びます。

0451:  endDrag: function(event) {
0452:    if(!this.dragging) return;
0453:    this.stopScrolling();
0454:    this.finishDrag(event, true);
0455:    Event.stop(event);
0456:  },
0457:  

451~457行目のendDragは、ドラッグの終了時に呼ばれる関数です。DraggablesのendDragイベントハンドラから呼ばれます。

453行目で、スクロールを中止します。

454行目で、finishDrag関数を、2番目の引数をtrueで呼びます。

0458:  draw: function(point) {
0459:    var pos = Position.cumulativeOffset(this.element);
0460:    if(this.options.ghosting) {
0461:      var r   = Position.realOffset(this.element);
0462:      pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
0463:    }
0464:    
0465:    var d = this.currentDelta();
0466:    pos[0] -= d[0]; pos[1] -= d[1];
0467:    
0468:    if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
0469:      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
0470:      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
0471:    }
0472:    
0473:    var p = [0,1].map(function(i){ 
0474:      return (point[i]-pos[i]-this.offset[i]) 
0475:    }.bind(this));
0476:    
0477:    if(this.options.snap) {
0478:      if(Object.isFunction(this.options.snap)) {
0479:        p = this.options.snap(p[0],p[1],this);
0480:      } else {
0481:      if(Object.isArray(this.options.snap)) {
0482:        p = p.map( function(v, i) {
0483:          return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this))
0484:      } else {
0485:        p = p.map( function(v) {
0486:          return (v/this.options.snap).round()*this.options.snap }.bind(this))
0487:      }
0488:    }}
0489:    
0490:    var style = this.element.style;
0491:    if((!this.options.constraint) || (this.options.constraint=='horizontal'))
0492:      style.left = p[0] + "px";
0493:    if((!this.options.constraint) || (this.options.constraint=='vertical'))
0494:      style.top  = p[1] + "px";
0495:    
0496:    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
0497:  },
0498:  

458~498行目のdraw関数は、ドラッグ中の要素を描画する関数です。ドラッグ中の要素の適切な位置は、Position.cumulativeOffsetやrealOffsetを駆使して求めます。

459行目のPosition.cumulativeOffsetで、ドキュメント基点からのオフセットを求めます。

460~463行目では、options.ghostingで要素のクローンを作った分、座標にズレが生じているので、補正します。Position.realOffsetで、スクロール可能なオブジェクトの階層を遡って、すべてのスクロール量を積算します。さらにそこから、ページ自体のスクロール量であるdeltaX、deltaYとの差分をとって、posを補正します。

466行目で、現在の要素の位置を引きます。

468~471行目で、options.scrollが有効で、かつ、それがwindowで無い場合は、options.scroll要素のスクロール量について補正します。

473行目で、pに、ドラッグ中の要素の実際の描画位置を求めます。このとき、ドラッグ開始時のマウスポインタと要素の位置のズレであるoffsetの量も補正します。

477~488行目で、optiotns.snapの設定がある場合は、もう一手間あります。

478行目で、それが関数なら、その呼び出しの結果をpにします。

481行目で、それが配列なら、座標をsnapの値で割ってからround関数で丸めてsnapの値をかけるという操作を行います。これはグラフにすると幅snapの階段状になることから、ドラッグの座標の動きが幅snapごとに引っかかるようになります。

484行目で、それが値なら、配列と同様の処理をします。

490~494行目で、実際にelement要素のCSSのleft、topプロパティの値に代入して、描画位置を更新します。このとき、options.constraintの制限があるときは、水平方向の動きだけ、あるいは、垂直方向の動きだけを、反映します。

0499:  stopScrolling: function() {
0500:    if(this.scrollInterval) {
0501:      clearInterval(this.scrollInterval);
0502:      this.scrollInterval = null;
0503:      Draggables._lastScrollPointer = null;
0504:    }
0505:  },
0506:  

499~506行目のstopScrollingは、スクロールを中止する関数です。

501行目で、タイマを無効にします。その他の状態も初期化します。

0507:  startScrolling: function(speed) {
0508:    if(!(speed[0] || speed[1])) return;
0509:    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
0510:    this.lastScrolled = new Date();
0511:    this.scrollInterval = setInterval(this.scroll.bind(this), 10);
0512:  },
0513:  

507~513行目のstartScrollingは、スクロールを開始する関数です。

511行目で、10ミリ秒ごとにscroll関数を呼ぶタイマをセットします。

510行目で、lastScrolledに、現在時刻を代入します。タイマの10ミリ秒間は信用できないので、ブラウザの現在時刻の差分に頼る仕組みになっています。

0514:  scroll: function() {
0515:    var current = new Date();
0516:    var delta = current - this.lastScrolled;
0517:    this.lastScrolled = current;
0518:    if(this.options.scroll == window) {
0519:      with (this._getWindowScroll(this.options.scroll)) {
0520:        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
0521:          var d = delta / 1000;
0522:          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
0523:        }
0524:      }
0525:    } else {
0526:      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
0527:      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
0528:    }
0529:    
0530:    Position.prepare();
0531:    Droppables.show(Draggables._lastPointer, this.element);
0532:    Draggables.notify('onDrag', this);
0533:    if (this._isScrollChild) {
0534:      Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
0535:      Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
0536:      Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
0537:      if (Draggables._lastScrollPointer[0] < 0)
0538:        Draggables._lastScrollPointer[0] = 0;
0539:      if (Draggables._lastScrollPointer[1] < 0)
0540:        Draggables._lastScrollPointer[1] = 0;
0541:      this.draw(Draggables._lastScrollPointer);
0542:    }
0543:    
0544:    if(this.options.change) this.options.change(this);
0545:  },
0546:  

514~546行目のscroll関数は、実際にスクロールを行う関数です。

516行目で、前回の時刻との差を計算します。

518~524行目で、options.scrollがwindowならば、scrollTo関数でブラウザのスクロールを動かします。

そうでなければ、525行目で、scrollLeft、同Topプロパティを更新して、要素のスクロールを動かします。

520行目で、Position.prepareを呼んで、スクロール位置の変更がうまく働くように調整します。

531行目で、このスクロールによって、ドラッグが上空にきたドロップ先があるかもしれません。それを表示します。

532行目で、Draggables.notifyで、'onDrag'関係のフックを呼びます。

533~542行目で、_isScrollChildがtrueのとき、つまり、element要素がoptions.scrollの子要素であるときは、このスクロールによってelement要素の位置関係に影響があるので、再描画する必要があります。

535~540行目で、スクロールによって生じたズレを求めて、_lastScrollPointerに加算します。

541行目で、drawメソッドで再描画します。

544行目で、options.changeフックがあれば呼びます。

0547:  _getWindowScroll: function(w) {
0548:    var T, L, W, H;
0549:    with (w.document) {
0550:      if (w.document.documentElement && documentElement.scrollTop) {
0551:        T = documentElement.scrollTop;
0552:        L = documentElement.scrollLeft;
0553:      } else if (w.document.body) {
0554:        T = body.scrollTop;
0555:        L = body.scrollLeft;
0556:      }
0557:      if (w.innerWidth) {
0558:        W = w.innerWidth;
0559:        H = w.innerHeight;
0560:      } else if (w.document.documentElement && documentElement.clientWidth) {
0561:        W = documentElement.clientWidth;
0562:        H = documentElement.clientHeight;
0563:      } else {
0564:        W = body.offsetWidth;
0565:        H = body.offsetHeight
0566:      }
0567:    }
0568:    return { top: T, left: L, width: W, height: H };
0569:  }
0570:});
0571:

547~571行目で、_getWindowScrollは、ブラウザが現在表示している領域を返す関数です。

550~556行目で、スクロールの位置を求めます。

550行目で、ブラウザの標準モードではdocumentElementを参照します。

553行目で、互換モードではbodyを参照します。そのscrollTopscrollLeftを調べます。

557~566行目で、領域のサイズを求めます。

557行目で、innerWidthはNetscapeとSafariとMozillaベースのブラウザがサポートしています。

560行目で、そのサポートがないとき、ブラウザの標準モードではdocumentElementを参照します。そのclientWidthclientHeightを調べます。

563行目で、互換モードではbodyを参照します。そのoffsetWidthoffsetHeightを調べます。

0572:Draggable._dragging = { };
0573:

572行目の_draggingは、現在ドラッグ中かどうか、ドラッグ可能要素ごとに真偽値で表すためのハッシュテーブルです。キーにドラッグ可能要素、値に真偽値をとります。

おすすめ記事

記事・ニュース一覧