2012-06-26 35 views
27

Tôi đã đọc qua nhiều chủ đề liên quan nhưng không ai trong số họ dường như cung cấp giải pháp.Làm thế nào để xử lý vị trí cuộn trên hashchange trong ứng dụng Backbone.js?

Điều tôi đang cố gắng thực hiện là xử lý thanh cuộn thông minh trong ứng dụng Backbone.js của mình. Giống như nhiều người khác, tôi có nhiều # mypage hash routes. Một số tuyến đường này có thứ bậc. ví dụ. Tôi có một trang danh sách liệt kê một số mục, tôi nhấp vào một mục trong danh sách. Sau đó, nó sẽ mở ra một trang # view/ITEMID.

Các trang của tôi đều chia sẻ cùng một div Nội dung trong bố cục HTML. Trên một thay đổi điều hướng, tôi tiêm một div mới đại diện cho xem cho tuyến đường đó vào div nội dung, thay thế bất cứ điều gì đã có trước đó.

Vì vậy, bây giờ vấn đề của tôi:

Nếu mục nằm trong danh sách, tôi có thể phải cuộn để đến đó. Khi tôi nhấp vào nó, hành vi xương sống "mặc định" là trang # view/ITEMID được hiển thị ở cùng một vị trí cuộn mà chế độ xem #list. Sửa chữa đủ dễ dàng; chỉ cần thêm $ (document) .scrollTop (0) bất cứ khi nào một khung nhìn mới được chèn vào.

Vấn đề là nếu tôi nhấn nút quay lại, tôi muốn quay lại chế độ xem #list ở vị trí cuộn trước đó.

Tôi đã cố gắng đưa ra giải pháp rõ ràng cho điều này. Lưu trữ bản đồ các tuyến đường để di chuyển vị trí trong bộ nhớ. Tôi viết vào bản đồ này vào đầu của trình xử lý cho sự kiện hashchange, nhưng trước khi khung nhìn mới thực sự được đưa vào DOM. Tôi đọc từ bản đồ ở cuối trình xử lý hashchange, sau khi khung nhìn mới nằm trong DOM.

Điều tôi nhận thấy là ở một nơi nào đó, trong Firefox ít nhất, đang cuộn trang như một phần của sự kiện hashchange, do thời gian mã ghi-bản đồ của tôi được gọi, tài liệu có vị trí cuộn đáng kinh ngạc chắc chắn không được người dùng thực hiện một cách rõ ràng.

Bất kỳ ai biết cách khắc phục điều này hoặc thực tiễn tốt nhất mà tôi nên sử dụng thay thế?

Tôi đã kiểm tra kỹ và không có thẻ neo nào trong DOM của tôi khớp với các băm tôi đang sử dụng.

+0

Bạn không có cơ hội sử dụng 'position(). Top' và có các phần tử cha có được' vị trí: bất cứ thứ gì' sau tải DOM? – Ohgodwhy

+0

không, tôi đã sử dụng $ (tài liệu) .scrollTop() cho cả việc đọc và viết vị trí cuộn – schematic

Trả lời

13

Giải pháp của tôi cho điều này đã trở thành thứ gì đó ít tự động hơn tôi muốn, nhưng ít nhất là nó nhất quán.

Đây là mã của tôi để lưu và khôi phục. Mã này đã được thực hiện khá nhiều từ nỗ lực của tôi đối với giải pháp thực tế của tôi, chỉ cần gọi nó trên các sự kiện khác nhau. "mềm" là một lá cờ mà điều này xuất phát từ một hành động trình duyệt (quay lại, chuyển tiếp hoặc nhấp chuột băm) trái ngược với lời gọi "cứng" tới Router.navigate(). Trong suốt cuộc gọi điều hướng(), tôi muốn cuộn lên trên cùng.

restoreScrollPosition: function(route, soft) { 
    var pos = 0; 
    if (soft) { 
     if (this.routesToScrollPositions[route]) { 
      pos = this.routesToScrollPositions[route]; 
     } 
    } 
    else { 
     delete this.routesToScrollPositions[route]; 
    } 
    $(window).scrollTop(pos); 
}, 

saveScrollPosition: function(route) { 
    var pos = $(window).scrollTop(); 
    this.routesToScrollPositions[route] = pos; 
} 

Tôi cũng sửa đổi Backbone.History để chúng ta có thể biết sự khác biệt giữa phản ứng với một sự thay đổi lịch sử "mềm" (trong đó kêu gọi checkUrl) so với lập trình gây ra một "cứng" thay đổi lịch sử. Nó chuyển cờ này đến cuộc gọi lại của Router.

_.extend(Backbone.History.prototype, { 

    // react to a back/forward button, or an href click. a "soft" route 
    checkUrl: function(e) { 
     var current = this.getFragment(); 
     if (current == this.fragment && this.iframe) 
      current = this.getFragment(this.getHash(this.iframe)); 
     if (current == this.fragment) return false; 
     if (this.iframe) this.navigate(current); 
     // CHANGE: tell loadUrl this is a soft route 
     this.loadUrl(undefined, true) || this.loadUrl(this.getHash(), true); 
    }, 

    // this is called in the whether a soft route or a hard Router.navigate call 
    loadUrl: function(fragmentOverride, soft) { 
     var fragment = this.fragment = this.getFragment(fragmentOverride); 
     var matched = _.any(this.handlers, function(handler) { 
      if (handler.route.test(fragment)) { 
       // CHANGE: tell Router if this was a soft route 
       handler.callback(fragment, soft); 
       return true; 
      } 
     }); 
     return matched; 
    }, 
}); 

Ban đầu, tôi đã cố gắng lưu và lưu khôi phục hoàn toàn trong trình xử lý hashchange. Cụ thể hơn, trong trình bao bọc gọi lại của Bộ định tuyến, hàm ẩn danh gọi trình xử lý tuyến đường thực tế của bạn.

route: function(route, name, callback) { 
    Backbone.history || (Backbone.history = new Backbone.History); 
    if (!_.isRegExp(route)) route = this._routeToRegExp(route); 
    if (!callback) callback = this[name]; 
    Backbone.history.route(route, _.bind(function(fragment, soft) { 

     // CHANGE: save scroll position of old route prior to invoking callback 
     // & changing DOM 
     displayManager.saveScrollPosition(foo.lastRoute); 

     var args = this._extractParameters(route, fragment); 
     callback && callback.apply(this, args); 
     this.trigger.apply(this, ['route:' + name].concat(args)); 

     // CHANGE: restore scroll position of current route after DOM was changed 
     // in callback 
     displayManager.restoreScrollPosition(fragment, soft); 
     foo.lastRoute = fragment; 

     Backbone.history.trigger('route', this, name, args); 
    }, this)); 
    return this; 
}, 

tôi muốn xử lý mọi thứ theo cách này vì nó cho phép tiết kiệm trong mọi trường hợp, cho dù một nhấp chuột href, trở lại nút, nút phía trước, hoặc điều hướng() gọi.

Trình duyệt có "tính năng" cố gắng nhớ cuộn của bạn trên một hashchange và di chuyển đến nó khi quay lại băm. Thông thường điều này sẽ rất tuyệt, và sẽ giúp tôi giải quyết mọi rắc rối khi tự mình thực hiện nó. Vấn đề là ứng dụng của tôi, giống như nhiều ứng dụng, thay đổi chiều cao của DOM từ trang này sang trang khác.

Ví dụ: Tôi đang ở chế độ xem #list cao và đã cuộn xuống dưới cùng, sau đó nhấp vào một mục và chuyển đến chế độ xem #detail ngắn không có thanh cuộn. Khi tôi nhấn nút Quay lại, trình duyệt sẽ cố gắng cuộn tôi đến vị trí cuối cùng mà tôi đã cho chế độ xem #list. Nhưng tài liệu không cao nhưng vẫn không thể làm được. Vào thời điểm tuyến đường của tôi cho #list được gọi và tôi hiển thị lại danh sách, vị trí cuộn bị mất.

Vì vậy, không thể sử dụng bộ nhớ cuộn được tích hợp sẵn của trình duyệt. Trừ khi tôi làm cho tài liệu có chiều cao cố định hoặc đã thực hiện một số thủ thuật DOM, điều mà tôi không muốn làm.

Hơn nữa, hành vi cuộn tích hợp sẽ làm rối loạn nỗ lực trên, vì lệnh gọi đến saveScrollPosition được thực hiện quá muộn - trình duyệt đã thay đổi vị trí cuộn lúc đó.

Giải pháp cho điều này, điều này cần phải rõ ràng, đã gọi saveScrollPosition từ Router.navigate() thay vì trình bao bọc gọi lại tuyến đường. Điều này đảm bảo rằng tôi đang lưu vị trí cuộn trước khi trình duyệt thực hiện bất kỳ điều gì trên hashchange.

route: function(route, name, callback) { 
    Backbone.history || (Backbone.history = new Backbone.History); 
    if (!_.isRegExp(route)) route = this._routeToRegExp(route); 
    if (!callback) callback = this[name]; 
    Backbone.history.route(route, _.bind(function(fragment, soft) { 

     // CHANGE: don't saveScrollPosition at this point, it's too late. 

     var args = this._extractParameters(route, fragment); 
     callback && callback.apply(this, args); 
     this.trigger.apply(this, ['route:' + name].concat(args)); 

     // CHANGE: restore scroll position of current route after DOM was changed 
     // in callback 
     displayManager.restoreScrollPosition(fragment, soft); 
     foo.lastRoute = fragment; 

     Backbone.history.trigger('route', this, name, args); 
    }, this)); 
    return this; 
}, 

navigate: function(route, options) { 
    // CHANGE: save scroll position prior to triggering hash change 
    nationalcity.displayManager.saveScrollPosition(foo.lastRoute); 
    Backbone.Router.prototype.navigate.call(this, route, options); 
}, 

Thật không may nó cũng có nghĩa là tôi luôn gọi điều hướng() nếu tôi muốn lưu vị trí cuộn, thay vì chỉ sử dụng href = "# myhash" trong mẫu của tôi.

Ồ tốt. Nó hoạt động. :-)

+0

Chỉ muốn cung cấp tín dụng rằng hầu hết mã ghi đè ở trên được lấy từ mã Backbone gốc, vì tôi cần thực hiện các chỉnh sửa nhỏ với mã hiện tại không thể được thực hiện theo cách OOP-ish hơn. – schematic

0

Tôi có bản sửa lỗi hơi kém cho việc này. Trong ứng dụng của tôi, tôi đã có một vấn đề tương tự. Tôi giải quyết nó bằng cách đặt xem danh sách và quan điểm mục vào một container với:

height: 100% 

Sau đó, tôi đặt cả xem danh sách và xem mục phải có:

overflow-y: auto 
height: 100% 

Sau đó, khi tôi bấm vào một mục, tôi ẩn danh sách và hiển thị chế độ xem mục. Bằng cách này khi tôi đóng mục và quay trở lại danh sách, tôi giữ vị trí của mình trong danh sách. Nó hoạt động với nút quay lại một lần, mặc dù rõ ràng nó không giữ lịch sử của bạn, do đó, nhiều lần nhấp vào nút quay lại sẽ không giúp bạn có được nơi bạn cần. Tuy nhiên, một giải pháp không có JS, vì vậy nếu nó đủ tốt ...

5

Một giải pháp đơn giản: Store vị trí của xem danh sách trên tất cả các sự kiện cuộn trong một biến:

var pos; 
$(window).scroll(function() { 
    pos = window.pageYOffset; 
}); 

Khi trở về từ chế độ xem mục, cuộn chế độ xem danh sách đến vị trí đã lưu trữ:

window.scrollTo(0, pos); 
+0

Vâng, điều này có tác dụng và nhắc tôi rằng chúng ta chỉ cần đính kèm một scrollTo vào bất kỳ sự kiện onShow nào (thông qua Marionette) để đảm bảo một cách thủ công ở đầu trang. – MBHNYC

0

@ Mirage114 Cảm ơn bạn đã đăng giải pháp của mình. Nó hoạt động như một say mê. Chỉ là một điều nhỏ, nó giả định rằng các hàm tuyến đường là đồng bộ. Nếu có hoạt động không đồng bộ, ví dụ tìm nạp dữ liệu từ xa trước khi hiển thị chế độ xem, thì cửa sổ sẽ được cuộn trước khi nội dung chế độ xem được thêm vào DOM.Trong trường hợp của tôi, tôi lưu trữ dữ liệu khi một tuyến đường được truy cập lần đầu tiên. Điều này là để khi người dùng nhấn nút quay lại/tiến trình của trình duyệt, thao tác không đồng bộ của tìm nạp dữ liệu sẽ bị tránh. Tuy nhiên, không phải lúc nào cũng có thể lưu trữ mọi dữ liệu bạn cần cho một tuyến đường.

Các vấn đề liên quan