39

In CSS, any image path is relative to the CSS file location.

f.ex if I put the CSS file in /media/css/mystyles.css and use something like

.background:url(../images/myimage.jpg);

The browser will look for the image in /media/images/myimage.jpg which makes sense.

Is it possible to do the same thing in javascript?

F.ex if I include /media/js/myscript.js and put this code in there:

var img = new Image();
img.src = '../images/myimage.jpg';

Th image is not found, since browser is using the HTML file as a starting point, instead of the script location. I would like to be able to use the script location as a starting point, just like CSS does. Is this possible?

David Hellsing
  • 106,495
  • 44
  • 176
  • 212
  • 3
    FWIW Gecko 2.0 has added a document.currentScript property. https://developer.mozilla.org/en/DOM/document.currentScript – mjhm Apr 04 '11 at 01:37
  • possible duplicate of [How might I get the script filename from within that script?](http://stackoverflow.com/questions/710957/how-might-i-get-the-script-filename-from-within-that-script) – Bergi Jul 17 '13 at 15:41
  • To the casual reader, help the feature reported by @mjhm monitored on caniuse.com. Upvote here: http://github.com/Fyrd/caniuse/issues/1099. – Stephan Mar 24 '16 at 15:17

13 Answers13

45

Searching the DOM for your own <script> tag as above is the usual method, yes.

However, you usually needn't search too hard: when you're in the body of the script — run at include-time — you know very well which <script> element you are: the last one. The rest of them can't have been parsed yet.

var scripts= document.getElementsByTagName('script');
var path= scripts[scripts.length-1].src.split('?')[0];      // remove any ?query
var mydir= path.split('/').slice(0, -1).join('/')+'/';  // remove last filename part of path

function doSomething() {
    img.src= mydir+'../images/myimage.jpeg';
}

This doesn't hold true if your script has been linked with <script defer> (or, in HTML5, <script async>). However, this is currently rarely used.

Community
  • 1
  • 1
bobince
  • 528,062
  • 107
  • 651
  • 834
  • more commonly used which also presents a problem is dynamic (ajax) loading since the scripts included are run and then tossed and never appear in the `scripts` array – ekkis Nov 23 '11 at 22:23
  • 11
    Asynchronous loading is getting more popular (and easier) which is I wouldn't definitelly rely on this... – Tomáš Zato Nov 22 '14 at 01:45
  • optionally you can scan all the script tags and look for one with your scripts file name, but even that is hit or miss because someone might have renamed it. – Ryan Mann Dec 14 '15 at 07:26
  • what if I want to get the directory name of one script imported in the html but with different ubication? – Jose Rojas Jan 27 '16 at 20:22
  • This answer here is soo much simpler: http://stackoverflow.com/questions/3097644/can-code-in-a-javascript-file-know-its-own-domain-url – Fandi Susanto Apr 02 '16 at 05:17
  • I used this method but stored the root path in a variable and loaded my other scripts afterwards. A simple workaround if you're dynamically appending scripts to the head. – DerpyNerd Jan 15 '17 at 19:13
  • 2
    Doesn't work for `defer` scripts and `async` is very unreliable too – Alex from Jitbit Apr 28 '20 at 18:27
21

On more recent browsers, you can use the document.currentScript property to obtain the HTMLScript element corresponding to that script, then query its src property.

Can I use indicates support by 70% of the web’s users, at the time of this writing. Apparently Internet Explorer doesn’t support it, but Edge does. MDC lists support as Chrome 29+, Edge, Firefox 4.0+, Gecko 2.0+, Opera 16+ and Safari 8+. The comment by @mjhm already pointed out this feature, but back then it sounded very experimental.

Community
  • 1
  • 1
MvG
  • 57,380
  • 22
  • 148
  • 276
  • http://caniuse.com doesn't monitor this feature yet. You can help with an upvote here: http://github.com/Fyrd/caniuse/issues/1099. – Stephan Mar 24 '16 at 15:19
  • @Stephan: Already [done that](https://github.com/Fyrd/caniuse/issues/1099#issuecomment-153670757). But others reading this answer may want to contribute, too. – MvG Mar 24 '16 at 21:47
  • Cool :) This feature receives 5 upvotes this month. It is ranked 8 on features to add list. – Stephan Mar 24 '16 at 23:15
  • 2
    It seems that it doesn't hold true anymore as per note at 'document.currentScript`: "This API has fallen out of favor in the implementer and standards community, as it globally exposes script or SVG script elements..." – Krzysztof Przygoda Mar 28 '18 at 20:34
  • 3
    "For (ES6) modules use [`import.meta`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import.meta) instead." – MDN – Shiva Prasad Nov 02 '21 at 06:00
  • 2023 NOTE: This is no longer encouraged because "This API has fallen out of favor in the implementer and standards community, as it globally exposes script or SVG script elements." – Blizzardengle Jul 07 '23 at 20:21
8

Inspired by bobince answer above, I wrote a jQuery version. Here is the code in one line:

var scriptpath = $("script[src]").last().attr("src").split('?')[0].split('/').slice(0, -1).join('/')+'/';

Edit: Filter the script tag by the src attribute, so that we get a src to work on.

Timm
  • 2,488
  • 2
  • 22
  • 25
Thomas - BeeDesk
  • 1,008
  • 1
  • 11
  • 12
  • 1
    I edited your one-liner; you needed to filter the script tag by the src attribute ("script[src]") in case the last script is inline. – Timm Jun 06 '12 at 14:30
6

As other posters mentioned you need to compute your base url for the script first, you can the script below to find it.

// find the base path of a script
var settings = {};
settings.basePath = null;

if (!settings.basePath) {
  (function (name) {
    var scripts = document.getElementsByTagName('script');

    for (var i = scripts.length - 1; i >= 0; --i) {
      var src = scripts[i].src;
      var l = src.length;
      var length = name.length;
      if (src.substr(l - length) == name) {
        // set a global propery here
        settings.basePath = src.substr(0, l - length);

      }
    }
  })('myfile.js');
}

log(settings.basePath);
mercator
  • 28,290
  • 8
  • 63
  • 72
yannis
  • 694
  • 11
  • 17
  • Fails if there are 2 script with same basename (but from different locations) included. I know this is unlikelly, but one would expect the there's a simple and reliable solution for such a basic thing. – Tomáš Zato Nov 22 '14 at 01:46
2

For asynchronous scripts, script tag walking won't do. So if: - performance.timing exists (like in new non-Safari browsers) & You have not reached its max, or have pushed-up its max before loading & Your script was the most recent thing loaded:

performance.getEntries().slice(-1)[0].name 

To push-up the max, do something like this:

performance.webkitSetResourceTimingBufferSize(10000)
user1212212
  • 1,311
  • 10
  • 6
1

(If base [Rubens's answer] doesn't work for you. Edit: Apparently he removed it, but I thought it was relevant; see base on the W3C site.)

It's possible, but it's a pain. :-) You have to search the DOM for the script tag that imported your script, and then grab its src value. That's how script.aculo.us does its module auto-loading; you can refer to the scriptaculous.js file for an example.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
1

I wrote a class to find get the path of scripts that works with delayed loading and async script tags. It's based on inspecting the stack trace and there are a couple of functions to format the results but it's intended to be basic. If nothing else use it as a reference.

function ScriptPath() {
  var scriptPath = '';
  try {
    //Throw an error to generate a stack trace
    throw new Error();
  }
  catch(e) {
    //Split the stack trace into each line
    var stackLines = e.stack.split('\n');
    var callerIndex = 0;
    //Now walk though each line until we find a path reference
    for(var i in stackLines){
      if(!stackLines[i].match(/http[s]?:\/\//)) continue;
      //We skipped all the lines with out an http so we now have a script reference
      //This one is the class constructor, the next is the getScriptPath() call
      //The one after that is the user code requesting the path info (so offset by 2)
      callerIndex = Number(i) + 2;
      break;
    }
    //Now parse the string for each section we want to return
    pathParts = stackLines[callerIndex].match(/((http[s]?:\/\/.+\/)([^\/]+\.js)):/);
  }

  this.fullPath = function() {
    return pathParts[1];
  };

  this.path = function() {
    return pathParts[2];
  };

  this.file = function() {
    return pathParts[3];
  };

  this.fileNoExt = function() {
    var parts = this.file().split('.');
    parts.length = parts.length != 1 ? parts.length - 1 : 1;
    return parts.join('.');
  };
}

Full source

QueueHammer
  • 10,515
  • 12
  • 67
  • 91
1

Es6 gives now an easy way to do this.

const currentScriptPath=function(level=0){
    let line=(new Error()).stack.split('\n')[level+1].split('@').pop();
    return line.substr(0,line.lastIndexOf('/')+1);
};
yorg
  • 600
  • 5
  • 7
1

One line "Pathfinder", when you know your script file name (here include.js):

// Script file name
var file = 'include.js';

// jQuery version
var path = (src = jQuery('script[src$="' + file + '"]').attr('src')).substring(0, src.lastIndexOf("/") + 1);
// jQuery with error handling
var path = (src = jQuery('script[src$="' + file + '"]').attr('src') ? src : 'Path/Not/Found/').substring(0, src.lastIndexOf("/") + 1);

// Plain JS version
var path = (src = document.querySelector('script[src$="' + file + '"]').src).substring(0, src.lastIndexOf("/") + 1);
// Plain JS with error handling
var path = (src = (script = document.querySelector('script[src$="' + file + '"]')) ? script.src : 'Path/Not/Found/').substring(0, src.lastIndexOf("/") + 1);
Krzysztof Przygoda
  • 1,217
  • 1
  • 17
  • 24
0

NOTE that all the samples on this question take LAST tag occurrence, and as pointed out not supporting async loading. so if you want to do it perfect manner, you should be matching YOUR script name ADDITIONALLY. in fact scriptaculous does that.

just it's a problem that the "incomplete" code is highlighted and projects like http://areaaperta.com/nicescroll/ use that version of the code

Adam Lear
  • 38,111
  • 12
  • 81
  • 101
glen
  • 1,657
  • 19
  • 19
0

You can flip through <head>'s childNodes, find your <script> tag, get the url of said script, and compute the url of your image.

Alsciende
  • 26,583
  • 9
  • 51
  • 67
0

One way is to put the path in the script itself:

var scriptPath = <?PHP echo json_encode(dirname($_SERVER['SCRIPT_NAME'])); ?>;
strager
  • 88,763
  • 26
  • 134
  • 176
0

In my case it isn't scripts.length-1.
Example:
In debug mode js file location: //server/js/app.js
In prod mode js file location: //server/js-build/app.js
The file name is known; the single way to find path by it's name:

getExecutionLocationFolder() {
    var fileName = "app.js";
    var scriptList = $("script[src]");
    var jsFileObject = $.grep(scriptList, function(el) {
        var currentElement = el.src.contains(fileName);
        return currentElement;
    });
    var jsFilePath = jsFileObject[0].src;
    var jsFileDirectory = jsFilePath.substring(0, jsFilePath.lastIndexOf("/") + 1);
    return jsFileDirectory;
};
Chaki_Black
  • 882
  • 3
  • 12
  • 30