使用 JavaScript 将相对路径转换为绝对路径

有一个函数,它给我的网址是这样的:

./some.css
./extra/some.css
../../lib/slider/slider.css

总是相对路径。

假设我们知道页面的当前路径,比如 http://site.com/stats/2012/,但不确定如何将这些相对路径转换为实际路径?

我们应该像这样:

./some.css => http://site.com/stats/2012/some.css
./extra/some.css => http://site.com/stats/2012/extra/some.css
../../lib/slider/slider.css => http://site.com/lib/slider/slider.css

没有 jQuery,只有普通的 javascript。

98127 次浏览

This should do it:

function absolute(base, relative) {
var stack = base.split("/"),
parts = relative.split("/");
stack.pop(); // remove current file name (or empty string)
// (omit if "base" is the current folder without trailing slash)
for (var i=0; i<parts.length; i++) {
if (parts[i] == ".")
continue;
if (parts[i] == "..")
stack.pop();
else
stack.push(parts[i]);
}
return stack.join("/");
}

Javascript will do it for you. There's no need to create a function.

var link = document.createElement("a");
link.href = "../../lib/slider/slider.css";
alert(link.protocol+"//"+link.host+link.pathname+link.search+link.hash);


// Output will be "http://www.yoursite.com/lib/slider/slider.css"

But if you need it as a function:

var absolutePath = function(href) {
var link = document.createElement("a");
link.href = href;
return (link.protocol+"//"+link.host+link.pathname+link.search+link.hash);
}

Update: Simpler version if you need the full absolute path:

var absolutePath = function(href) {
var link = document.createElement("a");
link.href = href;
return link.href;
}

The href solution only works once the document is loaded (at least in IE11). This worked for me:

link = link || document.createElement("a");
link.href =  document.baseURI + "/../" + href;
return link.href;

See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base

This from MDN is unbreakable!

/*\
|*|
|*|  :: translate relative paths to absolute paths ::
|*|
|*|  https://developer.mozilla.org/en-US/docs/Web/API/document.cookie
|*|
|*|  The following code is released under the GNU Public License, version 3 or later.
|*|  http://www.gnu.org/licenses/gpl-3.0-standalone.html
|*|
\*/


function relPathToAbs (sRelPath) {
var nUpLn, sDir = "", sPath = location.pathname.replace(/[^\/]*$/, sRelPath.replace(/(\/|^)(?:\.?\/+)+/g, "$1"));
for (var nEnd, nStart = 0; nEnd = sPath.indexOf("/../", nStart), nEnd > -1; nStart = nEnd + nUpLn) {
nUpLn = /^\/(?:\.\.\/)*/.exec(sPath.slice(nEnd))[0].length;
sDir = (sDir + sPath.substring(nStart, nEnd)).replace(new RegExp("(?:\\\/+[^\\\/]*){0," + ((nUpLn - 1) / 3) + "}$"), "/");
}
return sDir + sPath.substr(nStart);
}

Sample usage:

/* Let us be in /en-US/docs/Web/API/document.cookie */


alert(location.pathname);
// displays: /en-US/docs/Web/API/document.cookie


alert(relPathToAbs("./"));
// displays: /en-US/docs/Web/API/


alert(relPathToAbs("../Guide/API/DOM/Storage"));
// displays: /en-US/docs/Web/Guide/API/DOM/Storage


alert(relPathToAbs("../../Firefox"));
// displays: /en-US/docs/Firefox


alert(relPathToAbs("../Guide/././API/../../../Firefox"));
// displays: /en-US/docs/Firefox
function canonicalize(url) {
var div = document.createElement('div');
div.innerHTML = "<a></a>";
div.firstChild.href = url; // Ensures that the href is properly escaped
div.innerHTML = div.innerHTML; // Run the current innerHTML back through the parser
return div.firstChild.href;
}

This works on IE6 too, unlike some other solutions (see Getting an absolute URL from a relative one. (IE6 issue))

If you want to make a relative-to-absolute conversion for a link from a custom webpage in your browser (not for the page that runs your script), you can use a more enhanced version of the function suggested by @Bergi:

var resolveURL=function resolve(url, base){
if('string'!==typeof url || !url){
return null; // wrong or empty url
}
else if(url.match(/^[a-z]+\:\/\//i)){
return url; // url is absolute already
}
else if(url.match(/^\/\//)){
return 'http:'+url; // url is absolute already
}
else if(url.match(/^[a-z]+\:/i)){
return url; // data URI, mailto:, tel:, etc.
}
else if('string'!==typeof base){
var a=document.createElement('a');
a.href=url; // try to resolve url without base
if(!a.pathname){
return null; // url not valid
}
return 'http://'+url;
}
else{
base=resolve(base); // check base
if(base===null){
return null; // wrong base
}
}
var a=document.createElement('a');
a.href=base;


if(url[0]==='/'){
base=[]; // rooted path
}
else{
base=a.pathname.split('/'); // relative path
base.pop();
}
url=url.split('/');
for(var i=0; i<url.length; ++i){
if(url[i]==='.'){ // current directory
continue;
}
if(url[i]==='..'){ // parent directory
if('undefined'===typeof base.pop() || base.length===0){
return null; // wrong url accessing non-existing parent directories
}
}
else{ // child directory
base.push(url[i]);
}
}
return a.protocol+'//'+a.hostname+base.join('/');
}

It'll return null if something is wrong.

Usage:

resolveURL('./some.css', 'http://example.com/stats/2012/');
// returns http://example.com/stats/2012/some.css


resolveURL('extra/some.css', 'http://example.com/stats/2012/');
// returns http://example.com/stats/2012/extra/some.css


resolveURL('../../lib/slider/slider.css', 'http://example.com/stats/2012/');
// returns http://example.com/lib/slider/slider.css


resolveURL('/rootFolder/some.css', 'https://example.com/stats/2012/');
// returns https://example.com/rootFolder/some.css


resolveURL('localhost');
// returns http://localhost


resolveURL('../non_existing_file', 'example.com')
// returns null

I had to add a fix to the accepted solution because we can have slashes after # in our angularjs navigation.

function getAbsoluteUrl(base, relative) {
// remove everything after #
var hashPosition = base.indexOf('#');
if (hashPosition > 0){
base = base.slice(0, hashPosition);
}


// the rest of the function is taken from http://stackoverflow.com/a/14780463
// http://stackoverflow.com/a/25833886 - this doesn't work in cordova
// http://stackoverflow.com/a/14781678 - this doesn't work in cordova
var stack = base.split("/"),
parts = relative.split("/");
stack.pop(); // remove current file name (or empty string)
// (omit if "base" is the current folder without trailing slash)
for (var i=0; i<parts.length; i++) {
if (parts[i] == ".")
continue;
if (parts[i] == "..")
stack.pop();
else
stack.push(parts[i]);
}
return stack.join("/");
}

The proposed and accepted solution does not support server relative URLs and does not work on absolute URLs. If my relative is /sites/folder1 it won't work for example.

Here is another function that supports full, server relative or relative URLs as well as ../ for one level up. It is not perfect but covers a lot of options. Use this when your base URL is not the current page URL, otherwise there are better alternatives.

    function relativeToAbsolute(base, relative) {
//make sure base ends with /
if (base[base.length - 1] != '/')
base += '/';


//base: https://server/relative/subfolder/
//url: https://server
let url = base.substr(0, base.indexOf('/', base.indexOf('//') + 2));
//baseServerRelative: /relative/subfolder/
let baseServerRelative = base.substr(base.indexOf('/', base.indexOf('//') + 2));
if (relative.indexOf('/') === 0)//relative is server relative
url += relative;
else if (relative.indexOf("://") > 0)//relative is a full url, ignore base.
url = relative;
else {
while (relative.indexOf('../') === 0) {
//remove ../ from relative
relative = relative.substring(3);
//remove one part from baseServerRelative. /relative/subfolder/ -> /relative/
if (baseServerRelative !== '/') {
let lastPartIndex = baseServerRelative.lastIndexOf('/', baseServerRelative.length - 2);
baseServerRelative = baseServerRelative.substring(0, lastPartIndex + 1);
}
}
url += baseServerRelative + relative;//relative is a relative to base.
}


return url;
}

Hope this helps. It was really frustrating not to have this basic utility available in JavaScript.

The most simple, efficient and correct way to do so it to just use URL api.

new URL("http://www.stackoverflow.com?q=hello").href;
//=> "http://www.stackoverflow.com/?q=hello"


new URL("mypath","http://www.stackoverflow.com").href;
//=> "http://www.stackoverflow.com/mypath"


new URL("../mypath","http://www.stackoverflow.com/search").href
//=> "http://www.stackoverflow.com/mypath"


new URL("../mypath", document.baseURI).href
//=> "https://stackoverflow.com/questions/mypath"

Performance wise, this solution is on par with using string manipulation and twice as fast as creating a tag.

I found a very simple solution to do this while still supporting IE 10 (IE doesn't support the URL-API) by using the History API (IE 10 or higher). This solution works without any string manipulation.

function resolveUrl(relativePath) {
var originalUrl = document.location.href;
history.replaceState(history.state, '', relativePath);
var resolvedUrl = document.location.href;
history.replaceState(history.state, '', originalUrl);
return resolvedUrl;
}

history.replaceState() won't trigger browser navigation, but will still modify document.location and supports relative aswell as absolute paths.

The one drawback of this solution is that if you are already using the History-API and have set a custom state with a title, the current state's title is lost.

This will work. but only when you open a page with it's file name. it will not work well when you open a link like this stackoverflow.com/page. it will work with stackoverflow.com/page/index.php

function reltoabs(link){
let absLink = location.href.split("/");
let relLink = link;
let slashesNum = link.match(/[.]{2}\//g) ? link.match(/[.]{2}\//g).length : 0;
for(let i = 0; i < slashesNum + 1; i++){
relLink = relLink.replace("../", "");
absLink.pop();
}
absLink = absLink.join("/");
absLink += "/" + relLink;
return absLink;
}

I know this is a very old question, but you could do it with: (new URL(relativePath, location)).href.

Try:

/**
* Convert relative paths to absolute paths
* @author HaNdTriX
* @param {string} html - HTML string
* @param {string} baseUrl - base url to prepend to relative paths
* @param  {string[]} [attributes] - attributes to convert
* @returns {string}
*/
function absolutify(
html,
baseUrl,
attributes = [
"href",
"src",
"srcset",
"cite",
"background",
"action",
"formaction",
"icon",
"manifest",
"code",
"codebase",
]
) {
// Build the regex to match the attributes.
const regExp = new RegExp(
`(?<attribute>${attributes.join(
"|"
)})=(?<quote>['"])(?<path>.*?)\\k<quote>`,
"gi"
);


return html.replaceAll(regExp, (...args) => {
// Get the matched groupes
const { attribute, quote, path } = args[args.length - 1];


// srcset may have multiple paths `<url> <descriptor>, <url> <descriptor>`
if (attribute.toLowerCase() === "srcset") {
const srcSetParts = path.split(",").map((dirtyPart) => {
const part = dirtyPart.trim();
const [path, size] = part.split(" ");
return `${new URL(path.trim(), baseUrl).toString()} ${size || ""}`;
});


return `${attribute}=${quote}${srcSetParts.join(", ")}${quote}`;
}


const absoluteURL = new URL(path, baseUrl).href;
return `${attribute}=${quote}${absoluteURL}${quote}`;
});
}


console.log(
absolutify("<img src='./fooo.png'>", "https://example.com")
)