JSFiddle - React, Tailwind, and code Playground

HTML

<!-- Right click to change the pivot point -->

<!DOCTYPE html>
<html>
<head>
	<title>Rotation and Scale</title>
	<meta name='viewport' content='initial-scale=1.0,width=device-width'>
</head>
<body>
	<canvas id='scene'></canvas>
	<div class='ctrls'>
		<div class='ctrl-item' id='scale'>Scale</div>
		<div class='ctrl-item active' id='rotate'>Rotate</div>
	</div>
	<script src='https://threejs.org/build/three.min.js'></script>
</body>
</html>

CSS

body {
			padding: 0;
			margin: 0;
			font-family: arial;
			font-size: 16px;
			user-select: none;
		}

		#scene {
			position: absolute;
			left: 0;
			top: 0;
			width: 100%;
			height: 100%;
		}

		.ctrls {

			position: absolute;
			top: 0;
			left: 0;
			display: flex;
			flex-direction: row;
			padding: 10px;

		}

		.ctrl-item {

			border-radius: 1.55em;
			padding: 0.45em 0.85em;
			margin: 0 5px;
			color: #666;
			background: #fff;
			font-weight: 700;
			transition: all 0.2s ease-in;
			cursor: pointer;

		}

		.ctrl-item:active, .active {

			box-shadow: 0 0.11em 0.11em #ccc inset;
			color: #03a9f4;

		}

JavaScript

var renderer = new THREE.WebGLRenderer( {
  alpha: true,
  antialias: true,
  canvas: document.getElementById( 'scene' )
} );

renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0x333333 );

var scene = new THREE.Scene();

var frustumSize = 10;

var aspect = window.innerWidth / window.innerHeight;

var camera = new THREE.OrthographicCamera( -frustumSize * aspect, frustumSize * aspect, frustumSize, -frustumSize, 0, 10 );

var geometry = new THREE.PlaneGeometry( 5, 5 );
var material = new THREE.MeshBasicMaterial( { color: 'gold', side: THREE.DoubleSide } );
var mesh = new THREE.Mesh( geometry, material );



mesh.position.x = 0;
scene.add( mesh );

// universal back plane
var _plane = new THREE.Mesh( new THREE.PlaneGeometry( 10000, 10000 ), undefined );

var gizmoGeometry = new THREE.PlaneGeometry( 1, 1 );
var gizmoMaterial = new THREE.MeshBasicMaterial( { color: 'silver' } )
var gizmo = new THREE.Mesh( gizmoGeometry, gizmoMaterial );

gizmo.position.x = -4;

mesh.add( gizmo );

var pivotGeometry = new THREE.PlaneGeometry( 1, 1 );
var pivotMaterial = new THREE.MeshBasicMaterial( { color: 'black' } )
var pivot = new THREE.Mesh( pivotGeometry, pivotMaterial );

mesh.add( pivot );

var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();

var pointStart = new THREE.Vector3();
var pointEnd = new THREE.Vector3();

var canRotate = false;
var canScale = false;

document.oncontextmenu = function() { return false; }

var isRightMouseBtnDown = false;

function setPivot() {

  mesh.pivot.x = mouse.x * frustumSize * aspect;
  mesh.pivot.y = mouse.y * frustumSize;

  pivot.position.copy( mesh.pivot );

}

function onMouseDown( evt ) {

  mouse.set( 2.0 * evt.clientX / window.innerWidth - 1.0, -2.0 * evt.clientY / window.innerHeight + 1.0 );
  raycaster.setFromCamera( mouse, camera );

  if ( evt.button === 2 ) {

    isRightMouseBtnDown = true;
    setPivot();

  }

  var intersects = raycaster.intersectObject( gizmo )[ 0 ];

  if...