const preload = () => { let manager = new THREE.LoadingManager(); manager.onLoad = function() { const environment = new Environment( typo, particle ); } var typo = null; const loader = new THREE.FontLoader( manager ); const font = loader.load('https://res.cloudinary.com/dydre7amr/raw/upload/v1612950355/font_zsd4dr.json', function ( font ) { typo = font; }); const particle = new THREE.TextureLoader( manager ).load( 'https://res.cloudinary.com/dfvtkoboz/image/upload/v1605013866/particle_a64uzf.png'); } if ( document.readyState === "complete" || (document.readyState !== "loading" && !document.documentElement.doScroll)) preload (); else document.addEventListener("DOMContentLoaded", preload ); class Environment { constructor( font, particle ){ this.font = font; this.particle = particle; this.container = document.querySelector( '#magic' ); this.scene = new THREE.Scene(); this.createCamera(); this.createRenderer(); this.setup() this.bindEvents(); } bindEvents(){ window.addEventListener( 'resize', this.onWindowResize.bind( this )); } setup(){ this.createParticles = new CreateParticles( this.scene, this.font, this.particle, this.camera, this.renderer ); } render() { this.createParticles.render() this.renderer.render( this.scene, this.camera ) } createCamera() { this.camera = new THREE.PerspectiveCamera( 65, this.container.clientWidth / this.container.clientHeight, 1, 10000 ); this.camera.position.set( 0,0, 100 ); } createRenderer() { this.renderer = new THREE.WebGLRenderer(); this.renderer.setSize( this.container.clientWidth, this.container.clientHeight ); this.renderer.setPixelRatio( Math.min( window.devicePixelRatio, 2)); this.renderer.outputEncoding = THREE.sRGBEncoding; this.container.appendChild( this.renderer.domElement ); this.renderer.setAnimationLoop(() => { this.render() }) } onWindowResize(){ this.camera.aspect = this.container.clientWidth / this.container.clientHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize( this.container.clientWidth, this.container.clientHeight ); } } class CreateParticles { constructor( scene, font, particleImg, camera, renderer ) { this.scene = scene; this.font = font; this.particleImg = particleImg; this.camera = camera; this.renderer = renderer; this.raycaster = new THREE.Raycaster(); this.mouse = new THREE.Vector2(-200, 200); this.colorChange = new THREE.Color(); this.buttom = false; this.data = { text: 'FUTURE\nIS NOW', amount: 1500, particleSize: 1, particleColor: 0xffffff, textSize: 16, area: 250, ease: .05, } this.setup(); this.bindEvents(); } setup(){ const geometry = new THREE.PlaneGeometry( this.visibleWidthAtZDepth( 100, this.camera ), this.visibleHeightAtZDepth( 100, this.camera )); const material = new THREE.MeshBasicMaterial( { color: 0x00ff00, transparent: true } ); this.planeArea = new THREE.Mesh( geometry, material ); this.planeArea.visible = false; this.createText(); } bindEvents() { document.addEventListener( 'mousedown', this.onMouseDown.bind( this )); document.addEventListener( 'mousemove', this.onMouseMove.bind( this )); document.addEventListener( 'mouseup', this.onMouseUp.bind( this )); } onMouseDown(){ this.mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1; this.mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1; const vector = new THREE.Vector3( this.mouse.x, this.mouse.y, 0.5); vector.unproject( this.camera ); const dir = vector.sub( this.camera.position ).normalize(); const distance = - this.camera.position.z / dir.z; this.currenPosition = this.camera.position.clone().add( dir.multiplyScalar( distance ) ); const pos = this.particles.geometry.attributes.position; this.buttom = true; this.data.ease = .01; } onMouseUp(){ this.buttom = false; this.data.ease = .05; } onMouseMove( ) { this.mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1; this.mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1; } render( level ){ const time = ((.001 * performance.now())%12)/12; const zigzagTime = (1 + (Math.sin( time * 2 * Math.PI )))/6; this.raycaster.setFromCamera( this.mouse, this.camera ); const intersects = this.raycaster.intersectObject( this.planeArea ); if ( intersects.length > 0 ) { const pos = this.particles.geometry.attributes.position; const copy = this.geometryCopy.attributes.position; const coulors = this.particles.geometry.attributes.customColor; const size = this.particles.geometry.attributes.size; const mx = intersects[ 0 ].point.x; const my = intersects[ 0 ].point.y; const mz = intersects[ 0 ].point.z; for ( var i = 0, l = pos.count; i < l; i++) { const initX = copy.getX(i); const initY = copy.getY(i); const initZ = copy.getZ(i); let px = pos.getX(i); let py = pos.getY(i); let pz = pos.getZ(i); this.colorChange.setHSL( .5, 1 , 1 ) coulors.setXYZ( i, this.colorChange.r, this.colorChange.g, this.colorChange.b ) coulors.needsUpdate = true; size.array[ i ] = this.data.particleSize; size.needsUpdate = true; let dx = mx - px; let dy = my - py; const dz = mz - pz; const mouseDistance = this.distance( mx, my, px, py ) let d = ( dx = mx - px ) * dx + ( dy = my - py ) * dy; const f = - this.data.area/d; if( this.buttom ){ const t = Math.atan2( dy, dx ); px -= f * Math.cos( t ); py -= f * Math.sin( t ); this.colorChange.setHSL( .5 + zigzagTime, 1.0 , .5 ) coulors.setXYZ( i, this.colorChange.r, this.colorChange.g, this.colorChange.b ) coulors.needsUpdate = true; if ((px > (initX + 70)) || ( px < (initX - 70)) || (py > (initY + 70) || ( py < (initY - 70)))){ this.colorChange.setHSL( .15, 1.0 , .5 ) coulors.setXYZ( i, this.colorChange.r, this.colorChange.g, this.colorChange.b ) coulors.needsUpdate = true; } }else{ if( mouseDistance < this.data.area ){ if(i%5==0){ const t = Math.atan2( dy, dx ); px -= .03 * Math.cos( t ); py -= .03 * Math.sin( t ); this.colorChange.setHSL( .15 , 1.0 , .5 ) coulors.setXYZ( i, this.colorChange.r, this.colorChange.g, this.colorChange.b ) coulors.needsUpdate = true; size.array[ i ] = this.data.particleSize /1.2; size.needsUpdate = true; }else{ const t = Math.atan2( dy, dx ); px += f * Math.cos( t ); py += f * Math.sin( t ); pos.setXYZ( i, px, py, pz ); pos.needsUpdate = true; size.array[ i ] = this.data.particleSize * 1.3 ; size.needsUpdate = true; } if ((px > (initX + 10)) || ( px < (initX - 10)) || (py > (initY + 10) || ( py < (initY - 10)))){ this.colorChange.setHSL( .15, 1.0 , .5 ) coulors.setXYZ( i, this.colorChange.r, this.colorChange.g, this.colorChange.b ) coulors.needsUpdate = true; size.array[ i ] = this.data.particleSize /1.8; size.needsUpdate = true; } } } px += ( initX - px ) * this.data.ease; py += ( initY - py ) * this.data.ease; pz += ( initZ - pz ) * this.data.ease; pos.setXYZ( i, px, py, pz ); pos.needsUpdate = true; } } } createText(){ let thePoints = []; let shapes = this.font.generateShapes( this.data.text , this.data.textSize ); let geometry = new THREE.ShapeGeometry( shapes ); geometry.computeBoundingBox(); const xMid = - 0.5 * ( geometry.boundingBox.max.x - geometry.boundingBox.min.x ); const yMid = (geometry.boundingBox.max.y - geometry.boundingBox.min.y)/2.85; geometry.center(); let holeShapes = []; for ( let q = 0; q < shapes.length; q ++ ) { let shape = shapes[ q ]; if ( shape.holes && shape.holes.length > 0 ) { for ( let j = 0; j < shape.holes.length; j ++ ) { let hole = shape.holes[ j ]; holeShapes.push( hole ); } } } shapes.push.apply( shapes, holeShapes ); let colors = []; let sizes = []; for ( let x = 0; x < shapes.length; x ++ ) { let shape = shapes[ x ]; const amountPoints = ( shape.type == 'Path') ? this.data.amount/2 : this.data.amount; let points = shape.getSpacedPoints( amountPoints ) ; points.forEach( ( element, z ) => { const a = new THREE.Vector3( element.x, element.y, 0 ); thePoints.push( a ); colors.push( this.colorChange.r, this.colorChange.g, this.colorChange.b); sizes.push( 1 ) }); } let geoParticles = new THREE.BufferGeometry().setFromPoints( thePoints ); geoParticles.translate( xMid, yMid, 0 ); geoParticles.setAttribute( 'customColor', new THREE.Float32BufferAttribute( colors, 3 ) ); geoParticles.setAttribute( 'size', new THREE.Float32BufferAttribute( sizes, 1) ); const material = new THREE.ShaderMaterial( { uniforms: { color: { value: new THREE.Color( 0xffffff ) }, pointTexture: { value: this.particleImg } }, vertexShader: document.getElementById( 'vertexshader' ).textContent, fragmentShader: document.getElementById( 'fragmentshader' ).textContent, blending: THREE.AdditiveBlending, depthTest: false, transparent: true, } ); this.particles = new THREE.Points( geoParticles, material ); this.scene.add( this.particles ); this.geometryCopy = new THREE.BufferGeometry(); this.geometryCopy.copy( this.particles.geometry ); } visibleHeightAtZDepth ( depth, camera ) { const cameraOffset = camera.position.z; if ( depth < cameraOffset ) depth -= cameraOffset; else depth += cameraOffset; const vFOV = camera.fov * Math.PI / 180; return 2 * Math.tan( vFOV / 2 ) * Math.abs( depth ); } visibleWidthAtZDepth( depth, camera ) { const height = this.visibleHeightAtZDepth( depth, camera ); return height * camera.aspect; } distance (x1, y1, x2, y2){ return Math.sqrt(Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2)); } }