Commit eb6c83d0 authored by Master Q's avatar Master Q

init

parents
node_modules/
yarn.lock
\ No newline at end of file
>这篇文章就简单 用 ThreeJs 去实现是一个 月亮 围绕着地球的公转。
## 项目搭建
>这里就简单实用webpack + ts 进行搭建项目
1.简单的创建一个文件夹 `npm init` 初始化一个 项目,这里面一路回车下去就好了
![img](http://qnpic.top/2022-03-22-075409.jpg)
然后就开始安装我们的依赖 `yarn add three``yarn add @types/three ts-loader typescript webpack webpack-cli webpack-dev-server -D`
其中 `three` 就是我们的 生产依赖,  `types/three` 是 ts 的声明文件, ts-loader 和 typescript 是 我们的 ts, webpack webpack-cli webpack-dev-server 是我们的webpack 依赖
记得自己 生成下 tsconfig.json  然后配置 webpack.config.js. webpack 配置文件. 也是十分的简单, 这里的入口 文件是 src下的 main.ts 记得自己去创建一下, 比如我这边就只是打印了个 `console.log('three')`
```javascript
const path = require('path')
module.exports = {
mode: 'development',
devtool: 'source-map',
entry: {
index: './src/main.ts'
},
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /(node_modules)/,
use: [
{
loader: 'ts-loader',
}
],
exclude: /node_modules/
}
],
},
resolve: {
alias: {
'@': './src',
},
extensions: ['.ts', '.js', 'tsx']
},
output: { // 打包文件
filename: 'bundle.js',
path: path.resolve(__dirname, './dist')
},
devServer: { // webpack-dev-server 配置
port: '0.0.0.0',
hot: true,
open: true,
port: 9015
}
}
```
> 注意,我们上面那样配置 因为没有配置 public, 公共资源的配置项,所以,webpack 会默认 用 项目根目录下的 `public`文件夹,并且入口 html 是 index.html ,所以我们就需要去 配置这个 public/index.html
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three</title>
<style>
</style>
</head>
<body>
<!-- 这里的 bundle 就是 webpack 临时打包出来 -->
<script src="bundle.js"></script>
</body>
</html>
```
> 然后就是配置 我们的 项目命令 `npm set-script dev webpack-dev-server` 和 `npm set-script build webpack`,当然也可以手动去 package.json 里面去进行配置
>
> 最后 我们就可以 使用 `npm run dev` 启动我们的项目。
![](http://qnpic.top/2022-03-22-081035.jpg)
## 简答的 Three 基础
> Three.js是基于原生WebGL封装运行的三维引擎,在所有WebGL引擎中,Three.js是国内文资料最多、使用最广泛的三维引擎。是对WebGL的进行 封装使用,这里就不对 WebGL进行阐述了,咱也不懂-,-
简单的 Three 三大组成: 场景Scene, 相机 Camera,渲染器
### Scene 场景
> Scene 就比较容易理解,就是在3D 世界里面的 容器,这里面比较需要助力的是, 我们3D 坐标系 是 右手坐标系,就是 拿出我们的右手,分别用 大拇指,食指和中指 像如下。 z指向我们屏幕外
![](http://qnpic.top/2022-03-22-082251.jpg)
### Camera 相机
> 3D世界里,呈现给屏幕上就需要 Camera 相机,这很合理,所以在场景中就需要一个 相机进行 渲染图像,Camera的种类也有很多,我们主要用到的就是 正交相机`OrthographicCamera`和投影相机`PerspectiveCamera`, OrthographicCamera的 有着六个参数分别是 left, right, top, bottom, near, far, 在认知上可以理解有是一个长方体, 物体就是在长方体内,如果超出这个长方体就 不做展示渲染。 投影相机, 有着四个参数, 第一个参数是 角度,别的 自己百度下,这里 不做过多阐述,咱也不懂。
![](http://qnpic.top/2022-03-22-083951.jpg)
### 渲染器
> renderer 就老牛逼,用白话文就是 renderer 会 计算 scene和Camera 的矩阵数据,并通过 THREE 里面的计算 呈现给用户,这里做过多阐述了,咱也不懂
#### 简单实践
> 下面就简单实践一下, 东西太多了, 直接贴源码,有些我会 注释在里面,自己看一下,理解一下, 主要是实践,咱也不懂, 哦,还有 CSS2DObjct, CSS2DRenderer, OrbitControls 这里 并不是这里的重点,可以 自行注释掉, 然后 加上 camera.lockAt(this.scene.postion) 就好了
```typescript
import * as THREE from 'three'
import { CSS2DObjct, CSS2DRenderer } from './CSS2DRender'
import { OrbitControls } from './OrbitControls'
export class EarthAndMoon {
scene: THREE.Scene
camera: THREE.PerspectiveCamera
renderer: THREE.Renderer
twoDRenderer: CSS2DRenderer
worldWidth: number
worldHeight: number
textureLoader: THREE.TextureLoader
clock: THREE.Clock
moon: THREE.Mesh
constructor() {
this.textureLoader = new THREE.TextureLoader()
this.clock = new THREE.Clock()
this.initScene()
this.initCamera()
this.initGeometry()
this.initRenderer()
this.initEvents()
this.loop()
}
/**
* 初始化场景
*/
initScene() {
this.worldHeight = window.innerHeight
this.worldWidth = window.innerWidth
const scene = this.scene = new THREE.Scene()
// 创建 平行光, Three 里面的 光也会是有着很多种的, 比如 PointLight 点光源, DirectionalLight 平行光, SpotLight 聚光灯, AmbientLight 环境光
// 这里的 是模拟太阳光 平行光就 十分合适
const dirLight = new THREE.DirectionalLight(0xffffff)
dirLight.position.set(1, 1, 1)
scene.add(dirLight)
// 创建 debug的 坐标
scene.add(new THREE.AxesHelper(5))
// 创建光源debug
scene.add(new THREE.DirectionalLightHelper(dirLight))
}
/**
* 初始化相机
*/
initCamera() {
// 投影矩阵
// THREE.OrthographicCamera()
this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200)
this.camera.position.set(10, 5, 20)
// this.camera.lookAt(this.scene.position)
}
/**
* 初始化渲染器
*/
initRenderer() {
// 创建WebGL渲染器
const renderer = this.renderer = new THREE.WebGLRenderer()
renderer.setPixelRatio( window.devicePixelRatio );
this.renderer.setSize(this.worldWidth, this.worldHeight)
// 把renderer的 domElement 添加到 网页中
document.body.appendChild(this.renderer.domElement)
// 创建 CSS2D 渲染器
// CSS2DRenderer 这里面主要会有一个 render方法 通过入参 scene和 camera 来对 特定的Dom进行CSS渲染设置
const _css2dRenderer = this.twoDRenderer = new CSS2DRenderer()
_css2dRenderer.setSize( window.innerWidth, window.innerHeight );
_css2dRenderer.domElement.style.position = 'absolute';
_css2dRenderer.domElement.style.top = '0px';
document.body.appendChild( _css2dRenderer.domElement );
// OrbitControls Three 自带的 鼠标控制器,这里的入参就是 我们的相机
const controls = new OrbitControls(this.camera, this.renderer.domElement)
controls.minDistance = 5;
controls.maxDistance = 100;
}
/**
* 初始化事件
*/
initEvents() {
window.addEventListener( 'resize', () => {
// 计算 camera的角度值
this.camera.aspect = window.innerWidth / window.innerHeight;
// 更新 camerade 的投影矩阵
this.camera.updateProjectionMatrix();
this.renderer.setSize( window.innerWidth, window.innerHeight );
this.twoDRenderer.setSize( window.innerWidth, window.innerHeight );
});
}
/**
* 初始化几何体
*/
initGeometry() {
this.initEarthGeometry()
this.initMoonGeometry()
}
/**
* 初始化地球几何体
*/
initEarthGeometry() {
const textureLoader = this.textureLoader
// 创建球体
const earthGeometry = new THREE.SphereGeometry(1, 16, 16)
// MeshPhongMaterial 高光纹理,可以进行光照的漫反射和镜面反射
const earthMaterial = new THREE.MeshPhongMaterial({
specular: 0x333333, // 反光
shininess: 5, // 反光范围
map: textureLoader.load('./earth_atmos_2048.jpg'), // 地球上的贴图
specularMap: textureLoader.load( './earth_specular_2048.jpg' ),
normalMap: textureLoader.load( './earth_normal_2048.jpg' ),
normalScale: new THREE.Vector2( 0.85, 0.85 )
})
// 创建地球 THREE.Mesh 入参就是 几何体和 纹理
const earthMesh = new THREE.Mesh(earthGeometry, earthMaterial)
this.scene.add(earthMesh)
// CSS2DObjct 继承 Object3D 所以就可以 通过 add方法 添加到场景中,这里就直接加入到 earthMesh中 后面就可以通过
// twoDRenderer 进行渲染, 因为这里面会有着 isCSS2DObject 来进行区分
const earthLabel = new CSS2DObjct(document.createElement('div'), {
marginTop: '-1em',
color: '#fff',
}, {
className: 'label',
textContent: 'Earth'
})
earthLabel.position.set( 0, 1, 0 );
earthMesh.add(earthLabel)
}
/**
* 初始化月球几何体
*/
initMoonGeometry() {
// 下面就基本和 地球是一致的
const textureLoader = this.textureLoader
const moonGeometry = new THREE.SphereGeometry(0.75, 16, 16)
const moonMaterial = new THREE.MeshPhongMaterial({
shininess: 5, // 反光范围
map: textureLoader.load('./moon_1024.jpg'),
})
const moonMesh = this.moon = new THREE.Mesh(moonGeometry, moonMaterial)
moonMesh.position.x = 3
this.scene.add(moonMesh)
const moonLabel = new CSS2DObjct(document.createElement('div'), {marginTop: '-1em',
color: '#fff',}, {
className: 'label',
textContent: 'Moon'
})
moonLabel.position.set(0, 0.75, 0)
moonMesh.add(moonLabel)
}
/**
* 帧回调
*/
loop() {
const elapsed = this.clock.getElapsedTime();
// 计算月球的位置
this.moon.position.set( Math.sin( elapsed ) * 5, 0, Math.cos( elapsed ) * 5 );
this.renderer.render(this.scene, this.camera)
this.twoDRenderer.render(this.scene, this.camera)
requestAnimationFrame(this.loop.bind(this))
}
}
new EarthAndMoon()
```
> 简单的一个THREE 入门
{
"name": "webpackthreeprolesson",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server",
"build": "webpack"
},
"author": "",
"license": "ISC",
"dependencies": {
"three": "^0.138.3"
},
"devDependencies": {
"@types/three": "^0.138.0",
"ts-loader": "^9.2.8",
"typescript": "^4.6.2",
"webpack": "^5.70.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.7.4"
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three</title>
<style>
</style>
</head>
<body>
<!-- 这里的 bundle 就是 webpack 临时打包出来 -->
<script src="bundle.js"></script>
</body>
</html>
\ No newline at end of file
import { Camera, Matrix4, Object3D, Scene, Vector3 } from 'three'
type CSS2DObjctInfer = {
element?: HTMLElement,
isCSS2DObject?: boolean,
} & Object3D
export class CSS2DObjct extends Object3D {
isCSS2DObject: boolean = true
element: HTMLElement
constructor(element: HTMLElement=document.createElement('div'), style?: Partial<CSSStyleDeclaration>, attributes?: any) {
super()
this.element = element
this.element.style.position = 'absolute'
this.element.style.userSelect = 'none'
this.element.setAttribute('draggle', 'false')
if (style) {
for (var k in style) {
this.element.style[k] = style[k]
}
}
if (attributes) {
for (var k in attributes) {
// @ts-expect-error
this.element[k] = attributes[k]
}
}
this.addEventListener('removed', () => {
this.traverse(function(obj: any) {
if (obj.element instanceof Element && obj.element.parentNode !== null) {
obj.element.parentNode.removeChild(obj.element)
}
})
})
}
copy(source: this, recursive: boolean) {
super.copy(source, recursive)
// @ts-expect-error
this.element = source.element.cloneNode(true)
return this
}
}
const _vector = new Vector3();
const _viewMatrix = new Matrix4();
const _viewProjectionMatrix = new Matrix4();
const _a = new Vector3();
const _b = new Vector3();
interface CSS2DRendererProps {
element?: HTMLElement
}
export class CSS2DRenderer {
domElement: HTMLElement
render: (scene: any, camera: any) => void
setSize: (width: any, height: any) => void
constructor( parameters: CSS2DRendererProps = {} ) {
const _this = this;
let _width: number, _height: number;
let _widthHalf: number, _heightHalf: number;
const cache = {
objects: new WeakMap()
};
const domElement = parameters.element !== undefined ? parameters.element : document.createElement( 'div' );
domElement.style.overflow = 'hidden';
domElement.style.pointerEvents = 'none'
this.domElement = domElement;
// this.getSize = function () {
// return {
// width: _width,
// height: _height
// };
// };
this.render = function ( scene, camera ) {
if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
if ( camera.parent === null ) camera.updateMatrixWorld();
_viewMatrix.copy( camera.matrixWorldInverse );
_viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix );
renderObject( scene, scene, camera );
zOrder( scene );
};
this.setSize = function ( width, height ) {
_width = width;
_height = height;
_widthHalf = _width / 2;
_heightHalf = _height / 2;
domElement.style.width = width + 'px';
domElement.style.height = height + 'px';
};
function renderObject( object: CSS2DObjctInfer, scene: Scene, camera: Camera ) {
if ( object.isCSS2DObject ) {
// console.log(object)
// object.onBeforeRender( _this, scene, camera );
_vector.setFromMatrixPosition( object.matrixWorld );
_vector.applyMatrix4( _viewProjectionMatrix );
const element = object.element;
if ( /apple/i.test( navigator.vendor ) ) {
// https://github.com/mrdoob/three.js/issues/21415
element.style.transform = 'translate(-50%,-50%) translate(' + Math.round( _vector.x * _widthHalf + _widthHalf ) + 'px,' + Math.round( - _vector.y * _heightHalf + _heightHalf ) + 'px)';
} else {
element.style.transform = 'translate(-50%,-50%) translate(' + ( _vector.x * _widthHalf + _widthHalf ) + 'px,' + ( - _vector.y * _heightHalf + _heightHalf ) + 'px)';
}
element.style.display = ( object.visible && _vector.z >= - 1 && _vector.z <= 1 ) ? '' : 'none';
const objectData = {
distanceToCameraSquared: getDistanceToSquared( camera, object )
};
cache.objects.set( object, objectData );
if ( element.parentNode !== domElement ) {
domElement.appendChild( element );
}
// object.onAfterRender( _this, scene, camera );
}
for ( let i = 0, l = object.children.length; i < l; i ++ ) {
renderObject( object.children[i], scene, camera );
}
}
function getDistanceToSquared( object1: Camera, object2: CSS2DObjctInfer ) {
_a.setFromMatrixPosition( object1.matrixWorld );
_b.setFromMatrixPosition( object2.matrixWorld );
return _a.distanceToSquared( _b );
}
function filterAndFlatten( scene: { traverse: (arg0: (object: any) => void) => void } ) {
const result: any[] = [];
scene.traverse( function ( object: { isCSS2DObject: any } ) {
if ( object.isCSS2DObject ) result.push( object );
} );
return result;
}
function zOrder( scene: any ) {
const sorted = filterAndFlatten( scene ).sort( function ( a, b ) {
const distanceA = cache.objects.get( a ).distanceToCameraSquared;
const distanceB = cache.objects.get( b ).distanceToCameraSquared;
return distanceA - distanceB;
} );
const zMax = sorted.length;
for ( let i = 0, l = sorted.length; i < l; i ++ ) {
sorted[ i ].element.style.zIndex = zMax - i;
}
}
}
}
\ No newline at end of file
// @ts-nocheck
import * as THREE from 'three'
// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
//
// Orbit - left mouse / touch: one-finger move
// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
const _changeEvent = {
type: 'change'
};
const _startEvent = {
type: 'start'
};
const _endEvent = {
type: 'end'
};
export class OrbitControls extends THREE.EventDispatcher {
minDistance: number
maxDistance: number
maxZoom: number;
minZoom: number;
constructor( object, domElement ) {
super();
if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' );
if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' );
this.object = object;
this.domElement = domElement;
this.domElement.style.touchAction = 'none'; // disable touch scroll
// Set to false to disable this control
this.enabled = true; // "target" sets the location of focus, where the object orbits around
this.target = new THREE.Vector3(); // How far you can dolly in and out ( PerspectiveCamera only )
this.minDistance = 0;
this.maxDistance = Infinity; // How far you can zoom in and out ( OrthographicCamera only )
this.minZoom = 0;
this.maxZoom = Infinity; // How far you can orbit vertically, upper and lower limits.
// Range is 0 to Math.PI radians.
this.minPolarAngle = 0; // radians
this.maxPolarAngle = Math.PI; // radians
// How far you can orbit horizontally, upper and lower limits.
// If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI )
this.minAzimuthAngle = - Infinity; // radians
this.maxAzimuthAngle = Infinity; // radians
// Set to true to enable damping (inertia)
// If damping is enabled, you must call controls.update() in your animation loop
this.enableDamping = false;
this.dampingFactor = 0.05; // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
// Set to false to disable zooming
this.enableZoom = true;
this.zoomSpeed = 1.0; // Set to false to disable rotating
this.enableRotate = true;
this.rotateSpeed = 1.0; // Set to false to disable panning
this.enablePan = true;
this.panSpeed = 1.0;
this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up
this.keyPanSpeed = 7.0; // pixels moved per arrow key push
// Set to true to automatically rotate around the target
// If auto-rotate is enabled, you must call controls.update() in your animation loop
this.autoRotate = false;
this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60
// The four arrow keys
this.keys = {
LEFT: 'ArrowLeft',
UP: 'ArrowUp',
RIGHT: 'ArrowRight',
BOTTOM: 'ArrowDown'
}; // Mouse buttons
this.mouseButtons = {
LEFT: THREE.MOUSE.ROTATE,
MIDDLE: THREE.MOUSE.DOLLY,
RIGHT: THREE.MOUSE.PAN
}; // Touch fingers
this.touches = {
ONE: THREE.TOUCH.ROTATE,
TWO: THREE.TOUCH.DOLLY_PAN
}; // for reset
this.target0 = this.target.clone();
this.position0 = this.object.position.clone();
this.zoom0 = this.object.zoom; // the target DOM element for key events
this._domElementKeyEvents = null; //
// public methods
//
this.getPolarAngle = function () {
return spherical.phi;
};
this.getAzimuthalAngle = function () {
return spherical.theta;
};
this.getDistance = function () {
return this.object.position.distanceTo( this.target );
};
this.listenToKeyEvents = function ( domElement ) {
domElement.addEventListener( 'keydown', onKeyDown );
this._domElementKeyEvents = domElement;
};
this.saveState = function () {
scope.target0.copy( scope.target );
scope.position0.copy( scope.object.position );
scope.zoom0 = scope.object.zoom;
};
this.reset = function () {
scope.target.copy( scope.target0 );
scope.object.position.copy( scope.position0 );
scope.object.zoom = scope.zoom0;
scope.object.updateProjectionMatrix();
scope.dispatchEvent( _changeEvent );
scope.update();
state = STATE.NONE;
}; // this method is exposed, but perhaps it would be better if we can make it private...
this.update = function () {
const offset = new THREE.Vector3(); // so camera.up is the orbit axis
const quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
const quatInverse = quat.clone().invert();
const lastPosition = new THREE.Vector3();
const lastQuaternion = new THREE.Quaternion();
const twoPI = 2 * Math.PI;
return function update() {
const position = scope.object.position;
offset.copy( position ).sub( scope.target ); // rotate offset to "y-axis-is-up" space
offset.applyQuaternion( quat ); // angle from z-axis around y-axis
spherical.setFromVector3( offset );
if ( scope.autoRotate && state === STATE.NONE ) {
rotateLeft( getAutoRotationAngle() );
}
if ( scope.enableDamping ) {
spherical.theta += sphericalDelta.theta * scope.dampingFactor;
spherical.phi += sphericalDelta.phi * scope.dampingFactor;
} else {
spherical.theta += sphericalDelta.theta;
spherical.phi += sphericalDelta.phi;
} // restrict theta to be between desired limits
let min = scope.minAzimuthAngle;
let max = scope.maxAzimuthAngle;
if ( isFinite( min ) && isFinite( max ) ) {
if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI;
if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI;
if ( min <= max ) {
spherical.theta = Math.max( min, Math.min( max, spherical.theta ) );
} else {
spherical.theta = spherical.theta > ( min + max ) / 2 ? Math.max( min, spherical.theta ) : Math.min( max, spherical.theta );
}
} // restrict phi to be between desired limits
spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
spherical.makeSafe();
spherical.radius *= scale; // restrict radius to be between desired limits
spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); // move target to panned location
if ( scope.enableDamping === true ) {
scope.target.addScaledVector( panOffset, scope.dampingFactor );
} else {
scope.target.add( panOffset );
}
offset.setFromSpherical( spherical ); // rotate offset back to "camera-up-vector-is-up" space
offset.applyQuaternion( quatInverse );
position.copy( scope.target ).add( offset );
scope.object.lookAt( scope.target );
if ( scope.enableDamping === true ) {
sphericalDelta.theta *= 1 - scope.dampingFactor;
sphericalDelta.phi *= 1 - scope.dampingFactor;
panOffset.multiplyScalar( 1 - scope.dampingFactor );
} else {
sphericalDelta.set( 0, 0, 0 );
panOffset.set( 0, 0, 0 );
}
scale = 1; // update condition is:
// min(camera displacement, camera rotation in radians)^2 > EPS
// using small-angle approximation cos(x/2) = 1 - x^2 / 8
if ( zoomChanged || lastPosition.distanceToSquared( scope.object.position ) > EPS || 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
scope.dispatchEvent( _changeEvent );
lastPosition.copy( scope.object.position );
lastQuaternion.copy( scope.object.quaternion );
zoomChanged = false;
return true;
}
return false;
};
}();
this.dispose = function () {
scope.domElement.removeEventListener( 'contextmenu', onContextMenu );
scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
scope.domElement.removeEventListener( 'pointercancel', onPointerCancel );
scope.domElement.removeEventListener( 'wheel', onMouseWheel );
scope.domElement.removeEventListener( 'pointermove', onPointerMove );
scope.domElement.removeEventListener( 'pointerup', onPointerUp );
if ( scope._domElementKeyEvents !== null ) {
scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
} //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
}; //
// internals
//
const scope = this;
const STATE = {
NONE: - 1,
ROTATE: 0,
DOLLY: 1,
PAN: 2,
TOUCH_ROTATE: 3,
TOUCH_PAN: 4,
TOUCH_DOLLY_PAN: 5,
TOUCH_DOLLY_ROTATE: 6
};
let state = STATE.NONE;
const EPS = 0.000001; // current position in spherical coordinates
const spherical = new THREE.Spherical();
const sphericalDelta = new THREE.Spherical();
let scale = 1;
const panOffset = new THREE.Vector3();
let zoomChanged = false;
const rotateStart = new THREE.Vector2();
const rotateEnd = new THREE.Vector2();
const rotateDelta = new THREE.Vector2();
const panStart = new THREE.Vector2();
const panEnd = new THREE.Vector2();
const panDelta = new THREE.Vector2();
const dollyStart = new THREE.Vector2();
const dollyEnd = new THREE.Vector2();
const dollyDelta = new THREE.Vector2();
const pointers = [];
const pointerPositions = {};
function getAutoRotationAngle() {
return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
}
function getZoomScale() {
return Math.pow( 0.95, scope.zoomSpeed );
}
function rotateLeft( angle ) {
sphericalDelta.theta -= angle;
}
function rotateUp( angle ) {
sphericalDelta.phi -= angle;
}
const panLeft = function () {
const v = new THREE.Vector3();
return function panLeft( distance, objectMatrix ) {
v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
v.multiplyScalar( - distance );
panOffset.add( v );
};
}();
const panUp = function () {
const v = new THREE.Vector3();
return function panUp( distance, objectMatrix ) {
if ( scope.screenSpacePanning === true ) {
v.setFromMatrixColumn( objectMatrix, 1 );
} else {
v.setFromMatrixColumn( objectMatrix, 0 );
v.crossVectors( scope.object.up, v );
}
v.multiplyScalar( distance );
panOffset.add( v );
};
}(); // deltaX and deltaY are in pixels; right and down are positive
const pan = function () {
const offset = new THREE.Vector3();
return function pan( deltaX, deltaY ) {
const element = scope.domElement;
if ( scope.object.isPerspectiveCamera ) {
// perspective
const position = scope.object.position;
offset.copy( position ).sub( scope.target );
let targetDistance = offset.length(); // half of the fov is center to top of screen
targetDistance *= Math.tan( scope.object.fov / 2 * Math.PI / 180.0 ); // we use only clientHeight here so aspect ratio does not distort speed
panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
} else if ( scope.object.isOrthographicCamera ) {
// orthographic
panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
} else {
// camera neither orthographic nor perspective
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
scope.enablePan = false;
}
};
}();
function dollyOut( dollyScale ) {
if ( scope.object.isPerspectiveCamera ) {
scale /= dollyScale;
} else if ( scope.object.isOrthographicCamera ) {
scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
scope.object.updateProjectionMatrix();
zoomChanged = true;
} else {
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
scope.enableZoom = false;
}
}
function dollyIn( dollyScale ) {
if ( scope.object.isPerspectiveCamera ) {
scale *= dollyScale;
} else if ( scope.object.isOrthographicCamera ) {
scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
scope.object.updateProjectionMatrix();
zoomChanged = true;
} else {
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
scope.enableZoom = false;
}
} //
// event callbacks - update the object state
//
function handleMouseDownRotate( event ) {
rotateStart.set( event.clientX, event.clientY );
}
function handleMouseDownDolly( event ) {
dollyStart.set( event.clientX, event.clientY );
}
function handleMouseDownPan( event ) {
panStart.set( event.clientX, event.clientY );
}
function handleMouseMoveRotate( event ) {
rotateEnd.set( event.clientX, event.clientY );
rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
const element = scope.domElement;
rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
rotateStart.copy( rotateEnd );
scope.update();
}
function handleMouseMoveDolly( event ) {
dollyEnd.set( event.clientX, event.clientY );
dollyDelta.subVectors( dollyEnd, dollyStart );
if ( dollyDelta.y > 0 ) {
dollyOut( getZoomScale() );
} else if ( dollyDelta.y < 0 ) {
dollyIn( getZoomScale() );
}
dollyStart.copy( dollyEnd );
scope.update();
}
function handleMouseMovePan( event ) {
panEnd.set( event.clientX, event.clientY );
panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
pan( panDelta.x, panDelta.y );
panStart.copy( panEnd );
scope.update();
}
function handleMouseWheel( event ) {
if ( event.deltaY < 0 ) {
dollyIn( getZoomScale() );
} else if ( event.deltaY > 0 ) {
dollyOut( getZoomScale() );
}
scope.update();
}
function handleKeyDown( event ) {
let needsUpdate = false;
switch ( event.code ) {
case scope.keys.UP:
pan( 0, scope.keyPanSpeed );
needsUpdate = true;
break;
case scope.keys.BOTTOM:
pan( 0, - scope.keyPanSpeed );
needsUpdate = true;
break;
case scope.keys.LEFT:
pan( scope.keyPanSpeed, 0 );
needsUpdate = true;
break;
case scope.keys.RIGHT:
pan( - scope.keyPanSpeed, 0 );
needsUpdate = true;
break;
}
if ( needsUpdate ) {
// prevent the browser from scrolling on cursor keys
event.preventDefault();
scope.update();
}
}
function handleTouchStartRotate() {
if ( pointers.length === 1 ) {
rotateStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY );
} else {
const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX );
const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY );
rotateStart.set( x, y );
}
}
function handleTouchStartPan() {
if ( pointers.length === 1 ) {
panStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY );
} else {
const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX );
const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY );
panStart.set( x, y );
}
}
function handleTouchStartDolly() {
const dx = pointers[ 0 ].pageX - pointers[ 1 ].pageX;
const dy = pointers[ 0 ].pageY - pointers[ 1 ].pageY;
const distance = Math.sqrt( dx * dx + dy * dy );
dollyStart.set( 0, distance );
}
function handleTouchStartDollyPan() {
if ( scope.enableZoom ) handleTouchStartDolly();
if ( scope.enablePan ) handleTouchStartPan();
}
function handleTouchStartDollyRotate() {
if ( scope.enableZoom ) handleTouchStartDolly();
if ( scope.enableRotate ) handleTouchStartRotate();
}
function handleTouchMoveRotate( event ) {
if ( pointers.length == 1 ) {
rotateEnd.set( event.pageX, event.pageY );
} else {
const position = getSecondPointerPosition( event );
const x = 0.5 * ( event.pageX + position.x );
const y = 0.5 * ( event.pageY + position.y );
rotateEnd.set( x, y );
}
rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
const element = scope.domElement;
rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
rotateStart.copy( rotateEnd );
}
function handleTouchMovePan( event ) {
if ( pointers.length === 1 ) {
panEnd.set( event.pageX, event.pageY );
} else {
const position = getSecondPointerPosition( event );
const x = 0.5 * ( event.pageX + position.x );
const y = 0.5 * ( event.pageY + position.y );
panEnd.set( x, y );
}
panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
pan( panDelta.x, panDelta.y );
panStart.copy( panEnd );
}
function handleTouchMoveDolly( event ) {
const position = getSecondPointerPosition( event );
const dx = event.pageX - position.x;
const dy = event.pageY - position.y;
const distance = Math.sqrt( dx * dx + dy * dy );
dollyEnd.set( 0, distance );
dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
dollyOut( dollyDelta.y );
dollyStart.copy( dollyEnd );
}
function handleTouchMoveDollyPan( event ) {
if ( scope.enableZoom ) handleTouchMoveDolly( event );
if ( scope.enablePan ) handleTouchMovePan( event );
}
function handleTouchMoveDollyRotate( event ) {
if ( scope.enableZoom ) handleTouchMoveDolly( event );
if ( scope.enableRotate ) handleTouchMoveRotate( event );
} //
// event handlers - FSM: listen for events and reset state
//
function onPointerDown( event ) {
if ( scope.enabled === false ) return;
if ( pointers.length === 0 ) {
scope.domElement.setPointerCapture( event.pointerId );
scope.domElement.addEventListener( 'pointermove', onPointerMove );
scope.domElement.addEventListener( 'pointerup', onPointerUp );
} //
addPointer( event );
if ( event.pointerType === 'touch' ) {
onTouchStart( event );
} else {
onMouseDown( event );
}
}
function onPointerMove( event ) {
if ( scope.enabled === false ) return;
if ( event.pointerType === 'touch' ) {
onTouchMove( event );
} else {
onMouseMove( event );
}
}
function onPointerUp( event ) {
removePointer( event );
if ( pointers.length === 0 ) {
scope.domElement.releasePointerCapture( event.pointerId );
scope.domElement.removeEventListener( 'pointermove', onPointerMove );
scope.domElement.removeEventListener( 'pointerup', onPointerUp );
}
scope.dispatchEvent( _endEvent );
state = STATE.NONE;
}
function onPointerCancel( event ) {
removePointer( event );
}
function onMouseDown( event ) {
let mouseAction;
switch ( event.button ) {
case 0:
mouseAction = scope.mouseButtons.LEFT;
break;
case 1:
mouseAction = scope.mouseButtons.MIDDLE;
break;
case 2:
mouseAction = scope.mouseButtons.RIGHT;
break;
default:
mouseAction = - 1;
}
switch ( mouseAction ) {
case THREE.MOUSE.DOLLY:
if ( scope.enableZoom === false ) return;
handleMouseDownDolly( event );
state = STATE.DOLLY;
break;
case THREE.MOUSE.ROTATE:
if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
if ( scope.enablePan === false ) return;
handleMouseDownPan( event );
state = STATE.PAN;
} else {
if ( scope.enableRotate === false ) return;
handleMouseDownRotate( event );
state = STATE.ROTATE;
}
break;
case THREE.MOUSE.PAN:
if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
if ( scope.enableRotate === false ) return;
handleMouseDownRotate( event );
state = STATE.ROTATE;
} else {
if ( scope.enablePan === false ) return;
handleMouseDownPan( event );
state = STATE.PAN;
}
break;
default:
state = STATE.NONE;
}
if ( state !== STATE.NONE ) {
scope.dispatchEvent( _startEvent );
}
}
function onMouseMove( event ) {
if ( scope.enabled === false ) return;
switch ( state ) {
case STATE.ROTATE:
if ( scope.enableRotate === false ) return;
handleMouseMoveRotate( event );
break;
case STATE.DOLLY:
if ( scope.enableZoom === false ) return;
handleMouseMoveDolly( event );
break;
case STATE.PAN:
if ( scope.enablePan === false ) return;
handleMouseMovePan( event );
break;
}
}
function onMouseWheel( event ) {
if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return;
event.preventDefault();
scope.dispatchEvent( _startEvent );
handleMouseWheel( event );
scope.dispatchEvent( _endEvent );
}
function onKeyDown( event ) {
if ( scope.enabled === false || scope.enablePan === false ) return;
handleKeyDown( event );
}
function onTouchStart( event ) {
trackPointer( event );
switch ( pointers.length ) {
case 1:
switch ( scope.touches.ONE ) {
case THREE.TOUCH.ROTATE:
if ( scope.enableRotate === false ) return;
handleTouchStartRotate();
state = STATE.TOUCH_ROTATE;
break;
case THREE.TOUCH.PAN:
if ( scope.enablePan === false ) return;
handleTouchStartPan();
state = STATE.TOUCH_PAN;
break;
default:
state = STATE.NONE;
}
break;
case 2:
switch ( scope.touches.TWO ) {
case THREE.TOUCH.DOLLY_PAN:
if ( scope.enableZoom === false && scope.enablePan === false ) return;
handleTouchStartDollyPan();
state = STATE.TOUCH_DOLLY_PAN;
break;
case THREE.TOUCH.DOLLY_ROTATE:
if ( scope.enableZoom === false && scope.enableRotate === false ) return;
handleTouchStartDollyRotate();
state = STATE.TOUCH_DOLLY_ROTATE;
break;
default:
state = STATE.NONE;
}
break;
default:
state = STATE.NONE;
}
if ( state !== STATE.NONE ) {
scope.dispatchEvent( _startEvent );
}
}
function onTouchMove( event ) {
trackPointer( event );
switch ( state ) {
case STATE.TOUCH_ROTATE:
if ( scope.enableRotate === false ) return;
handleTouchMoveRotate( event );
scope.update();
break;
case STATE.TOUCH_PAN:
if ( scope.enablePan === false ) return;
handleTouchMovePan( event );
scope.update();
break;
case STATE.TOUCH_DOLLY_PAN:
if ( scope.enableZoom === false && scope.enablePan === false ) return;
handleTouchMoveDollyPan( event );
scope.update();
break;
case STATE.TOUCH_DOLLY_ROTATE:
if ( scope.enableZoom === false && scope.enableRotate === false ) return;
handleTouchMoveDollyRotate( event );
scope.update();
break;
default:
state = STATE.NONE;
}
}
function onContextMenu( event ) {
if ( scope.enabled === false ) return;
event.preventDefault();
}
function addPointer( event ) {
pointers.push( event );
}
function removePointer( event ) {
delete pointerPositions[ event.pointerId ];
for ( let i = 0; i < pointers.length; i ++ ) {
if ( pointers[ i ].pointerId == event.pointerId ) {
pointers.splice( i, 1 );
return;
}
}
}
function trackPointer( event ) {
let position = pointerPositions[ event.pointerId ];
if ( position === undefined ) {
position = new THREE.Vector2();
pointerPositions[ event.pointerId ] = position;
}
position.set( event.pageX, event.pageY );
}
function getSecondPointerPosition( event ) {
const pointer = event.pointerId === pointers[ 0 ].pointerId ? pointers[ 1 ] : pointers[ 0 ];
return pointerPositions[ pointer.pointerId ];
} //
scope.domElement.addEventListener( 'contextmenu', onContextMenu );
scope.domElement.addEventListener( 'pointerdown', onPointerDown );
scope.domElement.addEventListener( 'pointercancel', onPointerCancel );
scope.domElement.addEventListener( 'wheel', onMouseWheel, {
passive: false
} ); // force an update at start
this.update();
}
} // This set of controls performs orbiting, dollying (zooming), and panning.
// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
// This is very similar to OrbitControls, another set of touch behavior
//
// Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate
// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
// Pan - left mouse, or arrow keys / touch: one-finger move
export class MapControls extends OrbitControls {
constructor( object, domElement ) {
super( object, domElement );
this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up
this.mouseButtons.LEFT = THREE.MOUSE.PAN;
this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE;
this.touches.ONE = THREE.TOUCH.PAN;
this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE;
}
}
// // @ts-expect-error
// THREE.MapControls = MapControls;
// // @ts-expect-error
// THREE.OrbitControls = OrbitControls;
import * as THREE from 'three'
import { CSS2DObjct, CSS2DRenderer } from './CSS2DRender'
import { OrbitControls } from './OrbitControls'
export class EarthAndMoon {
scene: THREE.Scene
camera: THREE.PerspectiveCamera
renderer: THREE.Renderer
twoDRenderer: CSS2DRenderer
worldWidth: number
worldHeight: number
textureLoader: THREE.TextureLoader
clock: THREE.Clock
moon: THREE.Mesh
constructor() {
this.textureLoader = new THREE.TextureLoader()
this.clock = new THREE.Clock()
this.initScene()
this.initCamera()
this.initGeometry()
this.initRenderer()
this.initEvents()
this.loop()
}
/**
* 初始化场景
*/
initScene() {
this.worldHeight = window.innerHeight
this.worldWidth = window.innerWidth
const scene = this.scene = new THREE.Scene()
// 创建 平行光, Three 里面的 光也会是有着很多种的, 比如 PointLight 点光源, DirectionalLight 平行光, SpotLight 聚光灯, AmbientLight 环境光
// 这里的 是模拟太阳光 平行光就 十分合适
const dirLight = new THREE.DirectionalLight(0xffffff)
dirLight.position.set(1, 1, 1)
scene.add(dirLight)
// 创建 debug的 坐标
scene.add(new THREE.AxesHelper(5))
// 创建光源debug
scene.add(new THREE.DirectionalLightHelper(dirLight))
}
/**
* 初始化相机
*/
initCamera() {
// 投影矩阵
// THREE.OrthographicCamera()
this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200)
this.camera.position.set(10, 5, 20)
this.camera.lookAt(this.scene.position)
}
/**
* 初始化渲染器
*/
initRenderer() {
// 创建WebGL渲染器
const renderer = this.renderer = new THREE.WebGLRenderer()
renderer.setPixelRatio( window.devicePixelRatio );
this.renderer.setSize(this.worldWidth, this.worldHeight)
// 把renderer的 domElement 添加到 网页中
document.body.appendChild(this.renderer.domElement)
// 创建 CSS2D 渲染器
// CSS2DRenderer 这里面主要会有一个 render方法 通过入参 scene和 camera 来对 特定的Dom进行CSS渲染设置
// const _css2dRenderer = this.twoDRenderer = new CSS2DRenderer()
// _css2dRenderer.setSize( window.innerWidth, window.innerHeight );
// _css2dRenderer.domElement.style.position = 'absolute';
// _css2dRenderer.domElement.style.top = '0px';
// document.body.appendChild( _css2dRenderer.domElement );
// OrbitControls Three 自带的 鼠标控制器,这里的入参就是 我们的相机
// const controls = new OrbitControls(this.camera, this.renderer.domElement)
// controls.minDistance = 5;
// controls.maxDistance = 100;
}
/**
* 初始化事件
*/
initEvents() {
window.addEventListener( 'resize', () => {
// 计算 camera的角度值
this.camera.aspect = window.innerWidth / window.innerHeight;
// 更新 camerade 的投影矩阵
this.camera.updateProjectionMatrix();
this.renderer.setSize( window.innerWidth, window.innerHeight );
// this.twoDRenderer.setSize( window.innerWidth, window.innerHeight );
});
}
/**
* 初始化几何体
*/
initGeometry() {
this.initEarthGeometry()
this.initMoonGeometry()
}
/**
* 初始化地球几何体
*/
initEarthGeometry() {
const textureLoader = this.textureLoader
// 创建球体
const earthGeometry = new THREE.SphereGeometry(1, 16, 16)
// MeshPhongMaterial 高光纹理,可以进行光照的漫反射和镜面反射
const earthMaterial = new THREE.MeshPhongMaterial({
specular: 0x333333, // 反光
shininess: 5, // 反光范围
map: textureLoader.load('./earth_atmos_2048.jpg'), // 地球上的贴图
specularMap: textureLoader.load( './earth_specular_2048.jpg' ),
normalMap: textureLoader.load( './earth_normal_2048.jpg' ),
normalScale: new THREE.Vector2( 0.85, 0.85 )
})
// 创建地球 THREE.Mesh 入参就是 几何体和 纹理
const earthMesh = new THREE.Mesh(earthGeometry, earthMaterial)
this.scene.add(earthMesh)
// CSS2DObjct 继承 Object3D 所以就可以 通过 add方法 添加到场景中,这里就直接加入到 earthMesh中 后面就可以通过
// twoDRenderer 进行渲染, 因为这里面会有着 isCSS2DObject 来进行区分
// const earthLabel = new CSS2DObjct(document.createElement('div'), {
// marginTop: '-1em',
// color: '#fff',
// }, {
// className: 'label',
// textContent: 'Earth'
// })
// earthLabel.position.set( 0, 1, 0 );
// earthMesh.add(earthLabel)
}
/**
* 初始化月球几何体
*/
initMoonGeometry() {
// 下面就基本和 地球是一致的
const textureLoader = this.textureLoader
const moonGeometry = new THREE.SphereGeometry(0.75, 16, 16)
const moonMaterial = new THREE.MeshPhongMaterial({
shininess: 5, // 反光范围
map: textureLoader.load('./moon_1024.jpg'),
})
const moonMesh = this.moon = new THREE.Mesh(moonGeometry, moonMaterial)
moonMesh.position.x = 3
this.scene.add(moonMesh)
// const moonLabel = new CSS2DObjct(document.createElement('div'), {marginTop: '-1em',
// color: '#fff',}, {
// className: 'label',
// textContent: 'Moon'
// })
// moonLabel.position.set(0, 0.75, 0)
// moonMesh.add(moonLabel)
}
/**
* 帧回调
*/
loop() {
const elapsed = this.clock.getElapsedTime();
// 计算月球的位置
this.moon.position.set( Math.sin( elapsed ) * 5, 0, Math.cos( elapsed ) * 5 );
this.renderer.render(this.scene, this.camera)
// this.twoDRenderer.render(this.scene, this.camera)
requestAnimationFrame(this.loop.bind(this))
}
}
new EarthAndMoon()
\ No newline at end of file
{
"compilerOptions": {
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
"lib": [
"DOM",
"ES2015",
"ES5"
],
"sourceMap": true,
"noEmitOnError": true,
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
"strictNullChecks": false,
"esModuleInterop": true,
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
"exclude": [
"node_modules"
]
}
const path = require('path')
module.exports = {
mode: 'development',
devtool: 'source-map',
entry: {
index: './src/main.ts'
},
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /(node_modules)/,
use: [
{
loader: 'ts-loader',
}
],
exclude: /node_modules/
}
],
},
resolve: {
alias: {
'@': './src',
},
extensions: ['.ts', '.js', 'tsx']
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist')
},
devServer: {
port: '0.0.0.0',
hot: true,
open: true,
port: 9015
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment