วิธีการตรวจสอบว่ามีการเปลี่ยนแปลง URL หลังจากแฮชใน JavaScript


160

ฉันจะตรวจสอบว่า URL มีการเปลี่ยนแปลงใน JavaScript ได้อย่างไร ตัวอย่างเช่นเว็บไซต์เช่น GitHub ซึ่งใช้ AJAX จะต่อท้ายข้อมูลหน้าหลังสัญลักษณ์ # เพื่อสร้าง URL ที่ไม่ซ้ำโดยไม่ต้องโหลดหน้าซ้ำ วิธีที่ดีที่สุดในการตรวจสอบว่า URL นี้มีการเปลี่ยนแปลงอย่างไร

  • เป็นonloadเหตุการณ์ที่เรียกว่าอีกครั้งหรือไม่
  • มีตัวจัดการเหตุการณ์สำหรับ URL หรือไม่
  • หรือต้องตรวจสอบ URL ทุก ๆ วินาทีเพื่อตรวจสอบการเปลี่ยนแปลง?

1
สำหรับผู้เยี่ยมชมในอนาคตคำตอบใหม่โดย @aljgom จากปี 2018 เป็นทางออกที่ดีที่สุด: stackoverflow.com/a/52809105/151503
Redzarf

คำตอบ:


106

ในเบราว์เซอร์สมัยใหม่ (IE8 +, FF3.6 +, Chrome) คุณสามารถฟังhashchangeเหตุการณ์ที่เกิดขึ้นwindowได้

location.hashในเบราว์เซอร์เก่าบางท่านต้องจับเวลาว่าการตรวจสอบอย่างต่อเนื่อง หากคุณใช้ jQuery จะมีปลั๊กอินที่ใช้งานได้ทุกประการ


132
ตามที่ฉันเข้าใจแล้วจะทำงานเฉพาะการเปลี่ยนชิ้นส่วนหลังจากเครื่องหมาย # (ดังนั้นชื่อเหตุการณ์) และไม่ใช่สำหรับการเปลี่ยน URL แบบเต็มตามที่ดูเหมือนจะมีนัยโดยชื่อของคำถาม
NPC

13
@NPC ตัวจัดการสำหรับการเปลี่ยนแปลง URL แบบเต็ม (ไม่มีแท็กจุดยึด) หรือไม่
Neha Choudhary

คุณไม่ค่อยต้องการกิจกรรมหมดเวลาใช้เมาส์และคีย์บอร์ดเพื่อตรวจสอบ
Sjeiti

เกิดอะไรขึ้นถ้าเส้นทางเปลี่ยนไม่แฮช?
SuperUberDuper

@SuperUberDuper หากเส้นทางเปลี่ยนเนื่องจากผู้ใช้เริ่มต้นการนำทาง / คลิกลิงก์ ฯลฯ คุณจะเห็นเฉพาะbeforeunloadเหตุการณ์ หากรหัสของคุณเริ่มต้นการเปลี่ยนแปลง URL จะรู้ได้ดีที่สุด
Phihag

92

ฉันต้องการที่จะเพิ่มlocationchangeผู้ฟังเหตุการณ์ หลังจากการแก้ไขด้านล่างเราจะสามารถทำเช่นนี้ได้

window.addEventListener('locationchange', function(){
    console.log('location changed!');
})

ในทางตรงกันข้ามwindow.addEventListener('hashchange',()=>{})จะยิงก็ต่อเมื่อส่วนหลังแฮชแท็กใน URL มีการเปลี่ยนแปลงและwindow.addEventListener('popstate',()=>{})จะไม่ทำงานเสมอไป

การปรับเปลี่ยนนี้คล้ายกับคำตอบของคริสเตียนแก้ไขวัตถุประวัติเพื่อเพิ่มฟังก์ชันการทำงานบางอย่าง

โดยค่าเริ่มต้นมีpopstateเหตุการณ์ แต่มีเหตุการณ์ไม่และpushstatereplacestate

นี่เป็นการแก้ไขฟังก์ชั่นทั้งสามนี้เพื่อให้locationchangeเหตุการณ์ทั้งหมดเป็นเหตุการณ์ที่กำหนดเองเพื่อให้คุณใช้และยังรวมถึงpushstateและreplacestateเหตุการณ์หากคุณต้องการใช้สิ่งเหล่านี้:

/* These are the modifications: */
history.pushState = ( f => function pushState(){
    var ret = f.apply(this, arguments);
    window.dispatchEvent(new Event('pushstate'));
    window.dispatchEvent(new Event('locationchange'));
    return ret;
})(history.pushState);

history.replaceState = ( f => function replaceState(){
    var ret = f.apply(this, arguments);
    window.dispatchEvent(new Event('replacestate'));
    window.dispatchEvent(new Event('locationchange'));
    return ret;
})(history.replaceState);

window.addEventListener('popstate',()=>{
    window.dispatchEvent(new Event('locationchange'))
});

2
เยี่ยมมากสิ่งที่ฉันต้องการขอบคุณ
eslamb

ขอบคุณเป็นประโยชน์จริงๆ!
Thomas Lang

1
มีวิธีทำเช่นนี้ใน IE หรือไม่? ในขณะที่มันไม่รองรับ =>
joshuascotton

1
@joshuacotton => เป็นฟังก์ชั่นลูกศรคุณสามารถแทนที่ f => function fname () {... } ด้วย function (f) {return function fname () {... }}
aljgom

1
มันมีประโยชน์มากและใช้งานได้อย่างมีเสน่ห์ นี่ควรเป็นคำตอบที่ยอมรับได้
Kunal Parekh

77

ใช้รหัสนี้

window.onhashchange = function() { 
     //code  
}

ด้วย jQuery

$(window).bind('hashchange', function() {
     //code
});

7
จะต้องทำเครื่องหมายคำตอบ เป็นหนึ่งบรรทัดใช้โมเดลเหตุการณ์ของเบราว์เซอร์และไม่ต้องพึ่งพาการสิ้นเปลืองทรัพยากรอย่างไม่มีที่สิ้นสุด
Nick Mitchell

1
นี่คือความคิดของฉันคำตอบที่ดีที่สุดที่นี่ ไม่มีปลั๊กอินและโค้ดน้อยที่สุด
agDev

14
ไม่ทำงานกับการเปลี่ยนแปลง URL ที่ไม่ใช่แฮชซึ่งดูเหมือนว่าจะได้รับความนิยมอย่างมากเช่นการดำเนินการโดย Slack
NycCompSci

26
นี่เป็นคำตอบที่ดีที่สุดอย่างไรหากมีการเรียกใช้เมื่อมีแฮชใน url เท่านั้น
Aaron

1
คุณควรใช้ addEventListener แทนการแทนที่ค่า onhashchange โดยตรงในกรณีที่มีสิ่งอื่นที่ต้องการฟังเช่นกัน
broken-e

55

แก้ไขหลังจากการค้นคว้าเล็กน้อย:

ดูเหมือนว่าฉันถูกหลอกโดยเอกสารที่มีอยู่ในเอกสาร Mozilla popstateเหตุการณ์ (และฟังก์ชันการเรียกกลับของมันonpopstate) จะไม่ได้เรียกทุกครั้งpushState()หรือreplaceState()จะเรียกว่าในรหัส ดังนั้นคำตอบดั้งเดิมไม่สามารถใช้ได้ในทุกกรณี

อย่างไรก็ตามมีวิธีที่จะหลีกเลี่ยงสิ่งนี้โดยการปะแก้ลิงตามฟังก์ชันที่ @ alpha123 :

var pushState = history.pushState;
history.pushState = function () {
    pushState.apply(history, arguments);
    fireEvents('pushState', arguments);  // Some event-handling function
};

คำตอบเดิม

ระบุว่าชื่อของคำถามนี้คือ " วิธีตรวจจับการเปลี่ยนแปลง URL " คำตอบเมื่อคุณต้องการทราบว่าเมื่อใดที่การเปลี่ยนแปลงพา ธ เต็ม (และไม่ใช่แค่แฮชจุดยึด) คือคุณสามารถฟังpopstateเหตุการณ์ได้:

window.onpopstate = function(event) {
  console.log("location: " + document.location + ", state: " + JSON.stringify(event.state));
};

การอ้างอิงสำหรับ popstate ใน Mozilla Docs

ปัจจุบัน (ม.ค. 2017) มีการสนับสนุนสำหรับ popstate จาก 92%ของเบราว์เซอร์ทั่วโลก


11
ไม่น่าเชื่อว่าเราจะต้องหันไปใช้วิธีแฮ็กในปี 2018
แพะ

1
สิ่งนี้ใช้ได้กับกรณีการใช้งานของฉัน - แต่ก็เหมือนกับ @goat พูดว่า - ไม่น่าเชื่อว่าจะไม่มีการสนับสนุนดั้งเดิมในเรื่องนี้ ...
wasddd_

1
ข้อโต้แย้งอะไร ฉันจะตั้งค่ากิจกรรมได้อย่างไร
SeanMC

2
โปรดทราบว่านี่เป็นสิ่งที่ไม่น่าเชื่อถือในหลายกรณี ตัวอย่างเช่นจะไม่ตรวจพบการเปลี่ยนแปลง URL เมื่อคุณคลิกที่รูปแบบผลิตภัณฑ์ของ Amazon ที่แตกต่างกัน (ไทล์ที่อยู่ด้านล่างของราคา)
thdoan

2
สิ่งนี้จะไม่ตรวจพบการเปลี่ยนแปลงจาก localhost / foo เป็น localhost / baa หากไม่ได้ใช้ location.back ()
SuperUberDuper

53

ด้วย jquery (และปลั๊กอิน) คุณสามารถทำได้

$(window).bind('hashchange', function() {
 /* things */
});

http://benalman.com/projects/jquery-hashchange-plugin/

มิฉะนั้นใช่คุณจะต้องใช้ setInterval และตรวจสอบการเปลี่ยนแปลงในเหตุการณ์แฮช (window.location.hash)

Update! ร่างง่าย ๆ

function hashHandler(){
    this.oldHash = window.location.hash;
    this.Check;

    var that = this;
    var detect = function(){
        if(that.oldHash!=window.location.hash){
            alert("HASH CHANGED - new has" + window.location.hash);
            that.oldHash = window.location.hash;
        }
    };
    this.Check = setInterval(function(){ detect() }, 100);
}

var hashDetection = new hashHandler();

ฉันจะตรวจพบการเปลี่ยนแปลงของ (window.location) และจัดการกับมันได้หรือไม่ (ไม่มี jQuery)
BergP

6
คุณสามารถ @BergP โดยใช้ฟังจาวาสคริปต์ธรรมดา: window.addEventListener("hashchange", hashChanged);
รอน

1
ช่วงเวลาสั้น ๆ นี้เหมาะสำหรับแอพหรือไม่? นั่นคือมันจะไม่ทำให้เบราว์เซอร์ยุ่งเกินไปในการเรียกใช้detect()ฟังก์ชัน
Hasib Mahmud

4
@HasibMahmud รหัสนั้นกำลังตรวจสอบความเท่าเทียมกัน 1 ครั้งทุกๆ 100ms ฉันเพิ่งทำเบนช์มาร์กในเบราว์เซอร์ของฉันซึ่งฉันสามารถทำการตรวจสอบความเท่าเทียมกันได้ 500 ครั้งในเวลาไม่ถึง 1 มิลลิวินาที ดังนั้นรหัสนั้นใช้ 1 / 50,000 ของพลังการประมวลผลของฉัน ฉันจะไม่กังวลมากเกินไป
z5h

24

เพิ่มฟังเหตุการณ์เปลี่ยนแฮ!

window.addEventListener('hashchange', function(e){console.log('hash changed')});

หรือเพื่อฟังการเปลี่ยนแปลง URL ทั้งหมด:

window.addEventListener('popstate', function(e){console.log('url changed')});

สิ่งนี้ดีกว่าบางอย่างเช่นรหัสด้านล่างเพราะมีเพียงสิ่งเดียวเท่านั้นที่สามารถมีอยู่ใน window.onhashchange และคุณอาจจะเขียนทับรหัสของคนอื่น

// Bad code example

window.onhashchange = function() { 
     // Code that overwrites whatever was previously in window.onhashchange  
}

1
สถานะ pop จะเปิดใช้งานเฉพาะเมื่อคุณแสดงสถานะไม่ใช่กดหนึ่งครั้ง
SeanMC

8

วิธีนี้ใช้ได้ผลสำหรับฉัน:

var oldURL = "";
var currentURL = window.location.href;
function checkURLchange(currentURL){
    if(currentURL != oldURL){
        alert("url changed!");
        oldURL = currentURL;
    }

    oldURL = window.location.href;
    setInterval(function() {
        checkURLchange(window.location.href);
    }, 1000);
}

checkURLchange();

18
นี่เป็นวิธีพื้นฐานฉันคิดว่าเราสามารถตั้งเป้าหมายที่สูงขึ้นได้
Carles Alcolea

8
แม้ว่าฉันจะเห็นด้วยกับ @CarlesAlcolea ว่ามันให้ความรู้สึกเก่า แต่จากประสบการณ์ของฉันมันยังคงเป็นวิธีเดียวที่จะได้รับการเปลี่ยนแปลง URL ทั้งหมด 100%
Trev14

5
@ahofmann แนะนำ (ในการแก้ไขที่ควรจะเป็นความคิดเห็น) เปลี่ยนsetIntervalเป็นsetTimeout: "using setInterval () จะทำให้เบราว์เซอร์หยุดชะงักหลังจากผ่านไประยะหนึ่งเพราะมันจะสร้างการเรียกใหม่เพื่อตรวจสอบURLchange () ทุกวินาที setTimeout () เป็นคำตอบที่ถูกต้องเพราะมันถูกเรียกเพียงครั้งเดียว "
divibisan

นี่เป็นทางออกเดียวที่จะจับการเปลี่ยนแปลง URL ทั้งหมด แต่การใช้ทรัพยากรทำให้ฉันต้องหาวิธีแก้ปัญหาอื่น ๆ
ReturnTable

3
หรือแทนที่จะใช้setTimeoutเช่น @divibisan แนะนำให้ย้ายsetIntervalด้านนอกของฟังก์ชั่น checkURLchange();ก็กลายเป็นตัวเลือก
aristidesfl

4

แม้ว่าคำถามเก่า ๆ แล้วโครงการLocation-barนั้นมีประโยชน์มาก

var LocationBar = require("location-bar");
var locationBar = new LocationBar();

// listen to all changes to the location bar
locationBar.onChange(function (path) {
  console.log("the current url is", path);
});

// listen to a specific change to location bar
// e.g. Backbone builds on top of this method to implement
// it's simple parametrized Backbone.Router
locationBar.route(/some\-regex/, function () {
  // only called when the current url matches the regex
});

locationBar.start({
  pushState: true
});

// update the address bar and add a new entry in browsers history
locationBar.update("/some/url?param=123");

// update the address bar but don't add the entry in history
locationBar.update("/some/url", {replace: true});

// update the address bar and call the `change` callback
locationBar.update("/some/url", {trigger: true});

สำหรับ nodeJs เราต้องใช้ browserify เพื่อใช้งานฝั่งไคลเอ็นต์ ไม่ใช่พวกเราเหรอ
hack4mer

1
ไม่มันไม่ใช่ ทำงานในเบราว์เซอร์
Ray Booysen

3

หากต้องการฟังการเปลี่ยนแปลง URL โปรดดูด้านล่าง:

window.onpopstate = function(event) {
  console.log("location: " + document.location + ", state: " + JSON.stringify(event.state));
};

ใช้สไตล์นี้ถ้าคุณตั้งใจจะหยุด / ลบฟังหลังจากเงื่อนไขบางอย่าง

window.addEventListener('popstate', function(e) {
   console.log('url changed')
});

2

ในขณะที่ทำการขยายโครเมี่ยมเล็กน้อยฉันประสบปัญหาเดียวกันกับปัญหาเพิ่มเติม: บางครั้งหน้าเปลี่ยนไป แต่ไม่ใช่ URL

ตัวอย่างเช่นเพียงไปที่หน้าแรกของ Facebook และคลิกที่ปุ่ม 'หน้าแรก' คุณจะโหลดหน้าซ้ำ แต่ URL จะไม่เปลี่ยนแปลง (สไตล์แอปหนึ่งหน้า)

99% ของเวลาเราเป็นผู้พัฒนาเว็บไซต์เพื่อให้เราสามารถรับเหตุการณ์เหล่านั้นได้จาก Frameworks เช่น Angular, React, Vue เป็นต้น

แต่ในกรณีของส่วนขยาย Chrome (ใน Vanilla JS) ฉันต้องฟังเหตุการณ์ที่จะก่อให้เกิด "การเปลี่ยนหน้า" แต่ละครั้งซึ่งโดยทั่วไปแล้ว URL อาจถูกจับได้ แต่บางครั้งก็ไม่เป็นเช่นนั้น

โซลูชันโฮมเมดของฉันคือต่อไปนี้:

listen(window.history.length);
var oldLength = -1;
function listen(currentLength) {
  if (currentLength != oldLength) {
    // Do your stuff here
  }

  oldLength = window.history.length;
  setTimeout(function () {
    listen(window.history.length);
  }, 1000);
}

ดังนั้นโดยทั่วไปแล้วโซลูชัน leoneckert จะนำไปใช้กับประวัติหน้าต่างซึ่งจะเปลี่ยนไปเมื่อหน้ามีการเปลี่ยนแปลงในแอปหน้าเดียว

ไม่ใช่วิทยาศาสตร์จรวด แต่เป็นวิธีการแก้ปัญหาที่สะอาดที่สุดที่ฉันพบเพราะเรากำลังตรวจสอบความเสมอภาคจำนวนเต็มเท่านั้นไม่ใช่วัตถุที่ใหญ่กว่าหรือ DOM ทั้งหมด


วิธีการแก้ปัญหาของคุณนั้นง่ายและทำงานได้ดีมากสำหรับส่วนขยายของ Chrome ฉันอยากจะแนะนำให้ใช้รหัสวิดีโอ YouTube แทนความยาว stackoverflow.com/a/3452617/808901
Rod Lima

2
คุณไม่ควรใช้ setInterval เพราะทุกครั้งที่คุณเรียกใช้ Listen (xy) Interval ใหม่จะถูกสร้างขึ้นและคุณจะได้รับช่วงเวลานับพัน
Stef Chäser

1
คุณถูกต้องฉันสังเกตเห็นว่าหลังจากและไม่ได้เปลี่ยนโพสต์ของฉันฉันจะแก้ไขที่ ย้อนกลับไปในเวลาที่ฉันยังพบข้อผิดพลาดของ Google Chrome เนื่องจาก RAM รั่วไหล ขอบคุณสำหรับความคิดเห็น
Alburkerk

window.historyมีความยาวสูงสุด 50 (อย่างน้อย Chrome 80) หลังจากจุดนั้นให้window.history.lengthส่งคืน 50 เสมอเมื่อเกิดขึ้นวิธีนี้จะไม่สามารถรับรู้การเปลี่ยนแปลงใด ๆ
Collin Krawll

1

ดูที่ฟังก์ชันยกเลิกการโหลด jQuery มันจัดการกับทุกสิ่ง

https://api.jquery.com/unload/

เหตุการณ์ยกเลิกการโหลดถูกส่งไปยังองค์ประกอบหน้าต่างเมื่อผู้ใช้นำทางออกจากหน้า นี่อาจหมายถึงหนึ่งในหลาย ๆ สิ่ง ผู้ใช้สามารถคลิกที่ลิงค์เพื่อออกจากหน้าหรือพิมพ์ URL ใหม่ในแถบที่อยู่ ปุ่มไปข้างหน้าและย้อนกลับจะทำให้เกิดเหตุการณ์ การปิดหน้าต่างเบราว์เซอร์จะทำให้เหตุการณ์ถูกเรียกใช้ แม้แต่การโหลดซ้ำหน้าเว็บจะสร้างเหตุการณ์การยกเลิกการโหลดครั้งแรก

$(window).unload(
    function(event) {
        alert("navigating");
    }
);

2
โดยที่ Ajaxed อยู่ในเนื้อหา URL อาจเปลี่ยนแปลงได้โดยไม่ต้องยกเลิกการโหลดหน้าต่าง สคริปต์นี้ตรวจไม่พบการเปลี่ยนแปลง URL แม้ว่าอาจยังมีประโยชน์สำหรับผู้ใช้บางคนที่มีหน้าต่างที่ไม่ได้โหลดในทุกการเปลี่ยนแปลงของ URL
เดโบราห์

เช่นเดียวกับคำถาม @ranbuch นี่เป็นเฉพาะสำหรับหน้าเว็บที่ไม่ใช่แอปพลิเคชันหน้าเดียวและเหตุการณ์นี้เป็นเพียงการดูเหตุการณ์หน้าต่างยกเลิกการโหลดไม่ใช่การเปลี่ยน URL
ncubica

0
window.addEventListener("beforeunload", function (e) {
    // do something
}, false);

11
สิ่งนี้จะไม่ทำงานในบริบทของแอปพลิเคชันหน้าเดียวเนื่องจากเหตุการณ์การยกเลิกการโหลดจะไม่ทริกเกอร์
ncubica

0

คำตอบด้านล่างมาจากที่นี่ (ด้วยไวยากรณ์จาวาสคริปต์เก่า (ไม่มีฟังก์ชั่นลูกศร, รองรับ IE 10+)): https://stackoverflow.com/a/52809105/9168962

(function() {
  if (typeof window.CustomEvent === "function") return false; // If not IE
  function CustomEvent(event, params) {
    params = params || {bubbles: false, cancelable: false, detail: null};
    var evt = document.createEvent("CustomEvent");
    evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
    return evt;
  }
  window.CustomEvent = CustomEvent;
})();

(function() {
  history.pushState = function (f) {
    return function pushState() {
      var ret = f.apply(this, arguments);
      window.dispatchEvent(new CustomEvent("pushState"));
      window.dispatchEvent(new CustomEvent("locationchange"));
      return ret;
    };
  }(history.pushState);
  history.replaceState = function (f) {
    return function replaceState() {
      var ret = f.apply(this, arguments);
      window.dispatchEvent(new CustomEvent("replaceState"));
      window.dispatchEvent(new CustomEvent("locationchange"));
      return ret;
    };
  }(history.replaceState);
  window.addEventListener("popstate", function() {
    window.dispatchEvent(new CustomEvent("locationchange"));
  });
})();

0

อีกวิธีง่ายๆที่คุณสามารถทำได้คือการเพิ่มเหตุการณ์การคลิกผ่านชื่อคลาสไปยังแท็กจุดยึดบนหน้าเพื่อตรวจสอบเมื่อมีการคลิกจากนั้นคุณสามารถใช้ window.location.href เพื่อรับข้อมูล URL ซึ่ง คุณสามารถใช้เพื่อรันคำขอ ajax ของคุณไปยังเซิร์ฟเวอร์ ง่ายและสะดวก


-1

คุณกำลังเริ่มต้นใหม่setIntervalในแต่ละการโทรโดยไม่ต้องยกเลิกก่อนหน้า - คุณอาจหมายถึงการมีเพียงsetTimeout

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.