This is a three.js camera class based on my quaternion camera code, math is very similar with syntax changes due to vector and quaternion classes in three.js

based on https://github.com/mrdoob/three.js/blob/master/examples/js/controls/PointerLockControls.js

/**
 * @author hammad mazhar / http://hamelot.co.uk/
 * based on https://github.com/mrdoob/three.js/blob/master/examples/js/controls/PointerLockControls.js
 */
THREE.FreeCamera = function (camera) {

    var scope = this;

    camera.rotation.set(0, 0, 0);
    camera.position.set(0, 1, 0);
    camera.useQuaternion = true;

    var MOVE = {
        LEFT: {
            value: 0,
            name: "Left",
            code: "L"
        },
        RIGHT: {
            value: 1,
            name: "Right",
            code: "R"
        },
        FORWARD: {
            value: 2,
            name: "Forward",
            code: "F"
        },
        BACK: {
            value: 3,
            name: "Back",
            code: "B"
        },
        UP: {
            value: 4,
            name: "Up",
            code: "U"
        },
        DOWN: {
            value: 5,
            name: "Down",
            code: "D"
        }
    };
    var camera_scale = .5;
    var camera_direction = new THREE.Vector3(0, 0, 1);
    var camera_up = new THREE.Vector3(0, 1, 0);
    var camera_position_delta = new THREE.Vector3(0, 0, 0);
    var camera_position = new THREE.Vector3(0, 0, 0);
    var camera_look_at = new THREE.Vector3(0, 0, 2);

    var max_pitch_rate = 5.0;
    var max_heading_rate = 5.0;
    var camera_pitch = 0.0;
    var camera_heading = 0.0;

    var mouse_delta_x = 0.0;
    var mouse_delta_y = 0.0;
    var mouse_pos_x = 0.0;
    var mouse_pos_y = 0.0;
    var move_camera = false;

    var isOnObject = false;
    var canJump = false;

    var onMouseMove = function (event) {
        if (scope.enabled === false) return;

        var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
        var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;

        mouse_delta_x = mouse_pos_x - movementX;
        mouse_delta_y = mouse_pos_y - movementY;
        if (move_camera) {
            ChangeHeading(.08 * mouse_delta_x);
            ChangePitch(.08 * mouse_delta_y);
        }
        mouse_pos_x = movementX;
        mouse_pos_y = movementY;

    };
    var onMouseDown = function (event) {
        var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
        var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
        mouse_pos_x = movementX;
        mouse_pos_y = movementY;
        move_camera = true;
    }
    var onMouseUp = function (event) {
        var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
        var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
        mouse_pos_x = movementX;
        mouse_pos_y = movementY;
        move_camera = false;
    }
    var ChangePitch = function (degrees) {
        if (degrees < -max_pitch_rate) {
            degrees = -max_pitch_rate;
        } else if (degrees > max_pitch_rate) {
            degrees = max_pitch_rate;
        }
        camera_pitch += degrees;

        //Check bounds for the camera pitch
        if (camera_pitch > 360.0) {
            camera_pitch -= 360.0;
        } else if (camera_pitch < -360.0) {
            camera_pitch += 360.0;
        }
    }
    var ChangeHeading = function (degrees) {
        //Check bounds with the max heading rate so that we aren't moving too fast
        if (degrees < -max_heading_rate) {
            degrees = -max_heading_rate;
        } else if (degrees > max_heading_rate) {
            degrees = max_heading_rate;
        }
        //This controls how the heading is changed if the camera is pointed straight up or down
        //The heading delta direction changes
        if (camera_pitch > 90 && camera_pitch < 270 || (camera_pitch < -90 && camera_pitch > -270)) {
            camera_heading -= degrees;
        } else {
            camera_heading += degrees;
        }
        //Check bounds for the camera heading
        if (camera_heading > 360.0) {
            camera_heading -= 360.0;
        } else if (camera_heading < -360.0) {
            camera_heading += 360.0;
        }

    }

    var moveCamera = function (move) {
        if (scope.enabled === false) return;
        var t = new THREE.Vector3(0, 0, 0);
        switch (move) {
        case MOVE.UP:
            t.copy(camera_up);
            t.multiplyScalar(camera_scale);
            camera_position_delta.add(t);
            break;
        case MOVE.DOWN:
            t.copy(camera_up);
            t.multiplyScalar(camera_scale);
            camera_position_delta.sub(t);
            break;
        case MOVE.LEFT:
            t.crossVectors(camera_direction, camera_up);
            t.multiplyScalar(camera_scale);
            camera_position_delta.sub(t);
            break;
        case MOVE.RIGHT:
            t.crossVectors(camera_direction, camera_up);
            t.multiplyScalar(camera_scale);
            camera_position_delta.add(t);
            break;
        case MOVE.FORWARD:
            t.copy(camera_direction);
            t.multiplyScalar(camera_scale);
            camera_position_delta.add(t);
            break;
        case MOVE.BACK:
            t.copy(camera_direction);
            t.multiplyScalar(camera_scale);
            camera_position_delta.sub(t);
            break;
        }
    }
    var onKeyDown = function (event) {

        switch (event.keyCode) {
        case 81: //q
        case 33: //PgUp
            moveCamera(MOVE.DOWN);
            break;
        case 69: // e
        case 34: // PgDown
            moveCamera(MOVE.UP);
            break;
        case 38: // up
        case 87: // w
            moveCamera(MOVE.FORWARD);
            break;
        case 37: // left
        case 65: // a
            moveCamera(MOVE.LEFT);
            break;
        case 40: // down
        case 83: // s
            moveCamera(MOVE.BACK);
            break;
        case 39: // right
        case 68: // d
            moveCamera(MOVE.RIGHT);
            break;
        }

    };

    document.addEventListener('mousemove', onMouseMove, false);
    document.addEventListener('mousedown', onMouseDown, false);
    document.addEventListener('mouseup', onMouseUp, false);
    document.addEventListener('keydown', onKeyDown, false);

    this.enabled = false;

    this.getObject = function () {
        return camera;
    };

    this.isOnObject = function (boolean) {
        isOnObject = boolean;
        canJump = boolean;

    };

    this.getDirection = function () {
        // assumes the camera itself is not rotated
        return camera_direction;
    }();

    this.getLookAt = function (delta) {
        return camera_look_at;
    };

    this.update = function (delta) {
        var axis = new THREE.Vector3(0, 0, 0);
        //console.log(camera_direction);
        camera_direction.subVectors(camera_look_at, camera_position);
        camera_direction.normalize();
        axis.crossVectors(camera_direction, camera_up);
        var pitch_quat = new THREE.Quaternion(0, 0, 0, 1);
        var heading_quat = new THREE.Quaternion(0, 0, 0, 1);
        var temp = new THREE.Quaternion(0, 0, 0, 1);
        pitch_quat.setFromAxisAngle(axis, camera_pitch * Math.PI / 180.0);
        heading_quat.setFromAxisAngle(camera_up, camera_heading * Math.PI / 180.0);
        temp.multiplyQuaternions(pitch_quat, heading_quat);
        camera_direction.applyQuaternion(temp);
        camera_position.add(camera_position_delta);
        camera_look_at.addVectors(camera_position, camera_direction);

        camera.position.copy(camera_position);
        camera.up.copy(camera_up);
        camera.lookAt(camera_look_at);
        if (move_camera == false) {
            camera_pitch = camera_pitch * .5;
            camera_heading = camera_heading * .5;
        }
        camera_position_delta.multiplyScalar(.8);
    };
};