ThreeJS - How to maintain object position after scene resize

I’m a novice to ThreeJS and I have a new problem. I place an arrow in the scene , but then when I resize the window, the position of the arrow (relative to the scene background) changes. How do I prevent this?

Here is the “good” positioning, where the arrow comes out of the person’s head:

image

Here is the “bad” positioning, where the arrow has now moved to the side of the person’s head:

image

My index.html code:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">

  <!-- Imporant meta information to make the page as rigid as possible on mobiles, to avoid unintentional zooming on the page itself  -->
  <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Character Tutorial</title>

</head>
<body>

  <!-- The loading element overlays all else until the model is loaded, at which point we remove this element from the DOM -->
  <div class="loading" id="js-loader"><div class="loader"></div></div>

  <div class="wrapper">
    <!-- The canvas element is used to draw the 3D scene -->
    <canvas id="c"></canvas>

  </div>
  <!-- The main Three.js file -->
<!--  <script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/108/three.min.js'></script>-->
  <script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js'></script>

  <!-- This brings in the ability to load custom 3D objects in the .gltf file format. Blender allows the ability to export to this format out the box -->
<!--  <script src='https://cdn.jsdelivr.net/gh/mrdoob/Three.js@r92/examples/js/loaders/GLTFLoader.js'></script>-->

  <script  src="js/script.js"></script>

</body>
</html>

My script.js:

// noinspection DuplicatedCode

// Copied from https://stackoverflow.com/questions/63776448/threejs-applying-edge-geometry-to-arrowhelper
class CustomArrow extends THREE.Object3D {

    constructor( dir, origin, length, color, edgeColor, headLength, headWidth ) {

        super();
        // dir is assumed to be normalized

        this.type = 'CustomArrow';

        if ( dir === undefined ) dir = new THREE.Vector3( 0, 0, 1 );
        if ( origin === undefined ) origin = new THREE.Vector3( 0, 0, 0 );
        if ( length === undefined ) length = 1;
        if ( color === undefined ) color = 0xffff00;
        if ( headLength === undefined ) headLength = 0.2 * length;
        if ( headWidth === undefined ) headWidth = 0.2 * headLength;

        if ( this._lineGeometry === undefined ) {
            this._lineGeometry = new THREE.BufferGeometry();
            this._lineGeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0, 0, 1, 0 ], 3 ) );
            this._coneGeometry = new THREE.ConeBufferGeometry( 0.5, 1, 6);
            this._coneGeometry.translate( 0, - 0.5, 0 );
            this._axis = new THREE.Vector3();
        }

        this.position.copy( origin );

        this.line = new THREE.Line( this._lineGeometry, new THREE.LineBasicMaterial( { color: edgeColor, toneMapped: false, linewidth: 4 } ) );
        // this.line.matrixAutoUpdate = false;
        this.add( this.line )

        // base material
        this.cone = new THREE.Mesh( this._coneGeometry, new THREE.MeshBasicMaterial( { color: color, toneMapped: false } ) );
        this.add(this.cone);

        // wire frame
        this.wireframe = new THREE.Mesh( this._coneGeometry, new THREE.MeshBasicMaterial( {
            color: edgeColor,
            toneMapped: false,
            wireframe: true,
            wireframeLinewidth: 2 } ) );
        this.add(this.wireframe);

        this.setDirection( dir );
        this.setLength( length, headLength, headWidth );
    }

    setDirection( dir ) {

        // dir is assumed to be normalized

        if ( dir.y > 0.99999 ) {

            this.quaternion.set( 0, 0, 0, 1 );

        } else if ( dir.y < - 0.99999 ) {

            this.quaternion.set( 1, 0, 0, 0 );

        } else {

            this._axis.set( dir.z, 0, - dir.x ).normalize();

            const radians = Math.acos( dir.y );

            this.quaternion.setFromAxisAngle( this._axis, radians );

        }

    }

    setLength( length, headLength, headWidth ) {

        if ( headLength === undefined ) headLength = 0.2 * length;
        if ( headWidth === undefined ) headWidth = 0.2 * headLength;

        this.line.scale.set( 1, Math.max( 0.0001, length - headLength ), 1 ); // see #17458
        this.line.updateMatrix();

        this.cone.scale.set( headWidth, headLength, headWidth );
        this.cone.position.y = length;
        this.cone.updateMatrix();

        this.wireframe.scale.set( headWidth, headLength, headWidth );
        this.wireframe.position.y = length;
        this.wireframe.updateMatrix();
    }

    setColor( color ) {
        this.line.material.color.set( color );
        // this.cone.material.color.set( color );
        // this.wireframe.material.color.set( color );
    }

    copy( source ) {
        super.copy( source, false );
        this.line.copy( source.line );
        this.cone.copy( source.cone );
        this.wireframe.copy( source.wireframe );
        return this;
    }
}


(function() {
// Set our main variables
    let scene = new THREE.Scene(),
        renderer,
        camera,
        cone,
        backgroundLoader = new THREE.TextureLoader(),
        arrow,                              // Surface normal direction
        canRotate = false,
        arrowLength = 1,
        arrowHeadLength = 0.6,
        arrowHeadWidth = 0.4,
        pointer = new THREE.Vector3(),
        lookAt = new THREE.Vector3(),
        intersectPoint = new THREE.Vector3(),
        mouse = new THREE.Vector2(),        // for reuse
        plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0),
        raycaster = new THREE.Raycaster();  // Used to detect the click on our character

    init();

    function init() {

        const canvas = document.querySelector('#c');

        // Init the scene.
        scene.background = backgroundLoader.load('https://assets.imgix.net/hp/snowshoe.jpg');

        // Init the renderer.
        renderer = new THREE.WebGLRenderer({canvas, antialias: true});  // what is anti-aliasing?
        renderer.shadowMap.enabled = true;

        // What is difference between renderer.setSize and renderer.setPixelRatio?
        renderer.setPixelRatio(window.devicePixelRatio);
        document.body.appendChild(renderer.domElement);

        camera = new THREE.PerspectiveCamera(
            60,
            window.innerWidth / window.innerHeight,
            1,
            1000
        );
        camera.position.set(0, 0, 6);

        // Add arrow variables
        const arrowDirection = new THREE.Vector3();

        // Wrist
        // const arrowPosition = new THREE.Vector3(-0.14, .4, 0);

        // Left snow
        // const arrowPosition = new THREE.Vector3(-2, -1.5, 0);

        // Backpack
        const arrowPosition = new THREE.Vector3(0.3, 1.49, 0);

        // Calf
        // const arrowPosition = new THREE.Vector3(0.10, -0.85, 0);

        // const arrowPosition = new THREE.Vector3(0, 1, 0);
        arrowDirection.subVectors( scene.position, new THREE.Vector3(1, 1, 1)).normalize();
        // arrow = new THREE.ArrowHelper( arrowDirection, arrowPosition, arrowLength, 0xfffff00, arrowHeadLength, arrowHeadWidth);
        arrow = new CustomArrow( arrowDirection, arrowPosition, arrowLength, 0xfffff00, 0x000000, arrowHeadLength, arrowHeadWidth);
        arrow.castShadow = true;
        scene.add( arrow );
    }

    function update() {

        const canvas = renderer.domElement;
        let windowWidth = window.innerWidth;
        let windowHeight = window.innerHeight;
        let newAspect = canvas.clientWidth / canvas.clientHeight;
        let canvasPixelWidth = canvas.width / window.devicePixelRatio;
        let canvasPixelHeight = canvas.height / window.devicePixelRatio;

        const needResize = canvasPixelWidth !== windowWidth || canvasPixelHeight !== windowHeight;
        if (needResize) {
            // renderer.setSize(windowWidth, windowHeight, false);
            camera.aspect = newAspect;
            // camera.top = newAspect / 2;
            // camera.bottom = -newAspect / 2;
            // camera.left = newAspect / -2;
            // camera.right = newAspect / 2;
            camera.updateProjectionMatrix();
            renderer.setSize(windowWidth, windowHeight, false);
        }

        renderer.render(scene, camera);
        requestAnimationFrame(update);
    }
    update();

    // If user clicks, that toggles whether the arrow can move.
    document.addEventListener('click', function (e) {
        canRotate = ! canRotate;
    })

    document.addEventListener('mousemove', function (e) {
        if (canRotate) {

            pointer.x = (e.clientX / window.innerWidth) * 2 - 1;
            pointer.y = - (e.clientY / window.innerHeight) * 2 + 1;

            // Approach 0: https://stackoverflow.com/a/36071100/4570472
            let vector = new THREE.Vector3(pointer.x, pointer.y, 0.5);
            vector.unproject( camera );
            let remainder = arrowLength * arrowLength - vector.x * vector.x - vector.y * vector.y
            if (remainder <= 0){
                vector.z = 0;
            } else {
                vector.z = Math.sqrt(remainder);
            }

            let dir = vector.normalize();

            // Working, but doesn't track mouse exactly
            arrow.setDirection(dir);

        }
    });

})(); // Don't add anything below this line

/сс