how to solve CORS problem whith local html three.js?

85 Views Asked by At

I´ve been trying to make an sphere with three.js with an image texture but without a server, whether this a local image or an https image online it gives this error:

Access to image at 'file:///C:/Users//.....//sun.jpg' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, isolated-app, chrome-extension, chrome, https, chrome-untrusted. GET file:///C:/Users/...../sun.jpg net::ERR_FAILED

And here is the code I'm using:

const geometry = new THREE.SphereGeometry(5, 32, 32);
const cargador = new THREE.TextureLoader().load("sun.jpg");
const cargaTextura = new THREE.MeshBasicMaterial({
    map: cargador
});
const sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);
2

There are 2 best solutions below

1
seriously On

Browsers restrict loading files from the local file system.

You have several options:

  1. You can serve the image using a web server like express, nginx...

  2. Use a cloud storage bucket and access it via URI

  3. You can convert the image to base64 or something and display it directly by doing new THREE.TextureLoader().load(yourBase64string)

0
matt On

the file protocol, file:// is a protocol like any other, namely http://, https://, with some additional restrictions

when you referenced your sun.jpg, the browser took this to be a relative url and so looked to the base URL of the document to be able to resolve it, when it discovered it was from the file:// protocol it applied restrictions, one of them being you cant read local files, the actual error given is somewhat misleading

browsers have two native ways to access local files, the file input tag and drag and drop, both require interaction from user, we shall have to utilise those to access our local files, failing that you will either need a browser extension coupled with some form of executable or a http server

you can select which files you want to access using the file input tag, generate an internal url for them and use them wherever you would use any other url

<!-- local-files-with-three-js.html -->

<html>
      <head>
            <style>
                  body {
                        margin                : 40px;
                        font-family           : arial;
                  }
                  input {
                        font-size             : 16px;
                        padding               : 5px;
                  }
                  #user > * {
                        margin-top            : 20px;
                  }
                  #url {
                        display               : flex;
                        gap                   : 20px;
                        align-items           : center;
                  }
                  #url input {
                        width                 : 100%;
                  }
                  #file input {
                        width                 : 225px;
                  }
                  #mesh input {
                        width                 : 225px;
                  }
                  #three {
                        margin-top            : 30px;
                        display               : flex;
                        justify-content       : center;
                  }
            </style>
      </head>
      <body>
            <div id=user>
                  <div id=url><input placeholder='paste url here, hit enter' onchange='url_change(event)'></div>
                  <div id=file><input value='load texture from file' type=button onclick='file_change(event)'></div>
                  <div id=mesh><input type=button value=mesh onclick='mesh(event)'></div>
            </div>
            <div id=three></div>
      </body>
      
<script type=module>

      import * as THREE from 'https://esm.run/three';
      
      window.onload=function(){
      
            mesh();
            //texture(files.read('sun.jpg'));
            
      }//onload
      
      window.url_change=function(e){
      
            texture(e.target.value);
            e.target.value    = '';
            
      }//url_change
      
      window.file_change=function(e){
      
            var input   = document.createElement('input');
            input.type    = 'file';
            input.onchange    = onchange;
            input.click();
            
            function onchange(){
            
                  var blob    = input.files[0];
                  var url     = URL.createObjectURL(blob);
                  texture(url);
                  
            }//onchange
            
      }//file_change
      
      window.mesh=function(){
      
            var meshMaterial    = new THREE.MeshPhongMaterial({color:'lightblue',emissive:'blue',flatShading:true});
            draw(meshMaterial);
            
      }//mesh
      
      window.texture=function(url){
      
            var texture     = new THREE.TextureLoader().load(url);
            var material    = new THREE.MeshBasicMaterial({map:texture});
            draw(material);
            
      }//texture
      
      
      function draw(material){
      
            var scene           = new THREE.Scene();
            scene.background    = new THREE.Color('gray');
           
            var camera          = new THREE.PerspectiveCamera(75,1,1,50);
            camera.position.z   = 50;
           
            var renderer        = new THREE.WebGLRenderer();
            renderer.setSize(300,300);
            document.getElementById('three').replaceChildren(renderer.domElement);
           
            var lights          = new THREE.DirectionalLight('white',3);
            lights.position.set(75,75,200);
            scene.add(lights);
           
            //var geometry      = new THREE.BufferGeometry();
            const geometry      = new THREE.SphereGeometry(25, 32, 32);
            
            const sphere = new THREE.Mesh(geometry,material);
            scene.add(sphere);
            
            
            (function render() {
                    sphere.rotation.y  += 0.005;
                    renderer.render(scene,camera);
                    requestAnimationFrame(render);
            })();
           
      }//draw
      
</script>

<script>
                                                                                console.clear();
        var files       = {};
        files.read      = filename=>localStorage[filename];
        files.remove    = filename=>delete localStorage[filename];
        files.clear     = ()=>localStorage.clear();
        files.list      = ()=>{for(var key in {...localStorage})console.log(key)};
        
        
        //files.clear();
        files.list();
        
        var root                = document.createElement('div');
        root.style.padding      = '20px';
        root.style.border       = '2px dashed blue';
        document.body.insertBefore(root,document.body.firstChild);
        
        btn('select persistent files',create);
        btn('clear persistent files',files.clear);
        btn('list persistent files',files.list);
        
        function btn(value,onclick){
        
              var input                 = document.createElement('input');
              input.value               = value
              input.type                = 'button';
              input.onclick             = onclick;
              input.style.marginRight   = '20px';
              root.append(input);
              
        }//btn
        
        function create(e){
        
              var input         = document.createElement('input');
              input.type        = 'file';
              input.multiple    = true;
              input.onchange    = onchange;
              input.click();
              
              function onchange(e){
              
                    [...input.files].forEach(async file=>{
                                                                                console.log(file.name);
                          var url                   = await datauri(file);
                          localStorage[file.name]   = url;
                          
                    });
                    
              }//onchange
              
        }//create
        
        async function datauri(file){
        
              var i       = file.name.lastIndexOf('.');
              var ext     = file.name.slice(i+1);
              var mime    = 'image/'+ext;
              
              var buf     = await file.arrayBuffer();
              var bytes   = new Uint8Array(buf);
              var binary  = '';
              var n       = bytes.byteLength;
              for(var i=0;i<n;i++){
              
                    binary   += String.fromCharCode(bytes[i]);
                    
              }//for
              var b64       = window.btoa(binary);
              
              var datauri   = 'data:'+mime+';base64,'+b64;
              return datauri;
              
        }//datauri
        
</script>

</html>

https://jsbin.com/zosanurafo/

its quite cumbersome having to select which files you want to access every time you reload or change page, so i added the above script which allows persistent storage of files in localStorage, theres a limit of about 5MB

ok the above script will allow you to click the button "select files", and select multiple files, they will be available ( according to my brief testing in chrome ) across the entire file:// protocol, across page reloads and page navigation. anywhere you want to use these files just call

files.read(filename)       // e.g: files.read('sun.jpg')
                           // new THREE.TextureLoader().load(files.read("sun.jpg"));
    

with regards to CORS itself, the file:// protocol is still subject to CORS policies the same as any other protocol, for the file:// protocol, document.domain=null, or '' a special domain that always fails any domain check

that being said servers can send a CORS response, determined by access-control-* headers, if the appropiate CORS headers are received, the page can access certain restricted parts of that response

for pages under the file:// protocol the document.domain is effectively null, therefore any response will have to contain the response header Access-Control-Allow-Origin:* for javascript to be able to read the data, which is required for say THREE.textureLoader to work

.

to familiarise yourself with servers see

join us in the stackoverflow javascript chat

have fun



here you can try this to get you started, you'll need the internet to load three.js in this model

use the file button to select a file from your local harddrive

      
      function url_change(e){
          
          texture(e.target.value);
      }
      
      function file_change(e){
        
          var blob    = e.target.files[0];
          var url     = URL.createObjectURL(blob);
          texture(url);
      }
body {
  display:flex;
  align-items:top;
  gap:20px;
}
input {
  font-size:16px;
  padding:5px;
  width:250px;
  margin-left:20px;
}
span {
  display:inline-block;
}
<body>
  <span></span>
  <div>
        <input type=file onchange='file_change(event)'>
        <br>
        url<input onchange='url_change(event)'>
        <br>
        <input type=button value=mesh onclick='mesh(event)'>
  </div>
</body>

<script type=module>
      
      import * as THREE from 'https://esm.run/three';

      window.mesh=function(){
        
            var meshMaterial    = new THREE.MeshPhongMaterial({color:'lightblue',emissive:'blue',flatShading:true});
            ready(meshMaterial);
      
      }            
      
      window.texture=function(url){
        
            const cargador = new THREE.TextureLoader().load(url); 
            const cargaTextura = new THREE.MeshBasicMaterial({ map: cargador }); 
            ready(cargaTextura);
            
      }
      
      function ready(material){
        
            var scene           = new THREE.Scene();
            scene.background    = new THREE.Color('gray');
          
            var camera          = new THREE.PerspectiveCamera(75,1,1,50);
            camera.position.z   = 50;
          
            var renderer        = new THREE.WebGLRenderer();
            renderer.setSize(300,300);
            document.body.querySelector('span').replaceChildren(renderer.domElement);
          
            var lights          = new THREE.DirectionalLight('white',3);
            lights.position.set(75,75,200);
            scene.add(lights);
          
            //var geometry      = new THREE.BufferGeometry();
            const geometry      = new THREE.SphereGeometry(25, 32, 32); 
        
            const sphere = new THREE.Mesh(geometry,material); 
            scene.add(sphere);
      
      
            (function render() {
                    sphere.rotation.y  += 0.005;
                    renderer.render(scene,camera);
                    requestAnimationFrame(render);
            })();

      }//setup
      
      mesh();
      
</script>

you can paste a url into the text field and press enter

or click the mesh button