Created
February 3, 2021 18:59
-
-
Save AnansiOmega/16f07be6823258f09ece72bf3e8feba1 to your computer and use it in GitHub Desktop.
Particle Text Effect
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <div id="three-container"></div> | |
| <div id="instructions"> | |
| click and drag to control the animation | |
| </div> | |
| <script src='http://cdnjs.cloudflare.com/ajax/libs/three.js/r75/three.min.js'></script> | |
| <script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/175711/bas.js'></script> | |
| <script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/175711/TextGeometry.js'></script> | |
| <script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/175711/FontUtils.js'></script> | |
| <script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/175711/pnltri.min.js'></script> | |
| <script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/175711/droid_sans_bold.typeface.js'></script> | |
| <script src='http://cdnjs.cloudflare.com/ajax/libs/gsap/1.18.0/TweenMax.min.js'></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //capture the window loading | |
| window.onload = init; | |
| function init() { | |
| var root = new THREERoot({ | |
| createCameraControls: false, | |
| antialias: true, | |
| fov: 60 | |
| }); | |
| //setup the scene | |
| root.renderer.setClearColor(0x3aafa9); | |
| root.renderer.setPixelRatio(window.devicePixelRatio || 1); | |
| root.camera.position.set(0, 0, 400); | |
| //create the text animation variable | |
| var textAnimation = createTextAnimation(); | |
| textAnimation.position.y = -10; | |
| root.scene.add(textAnimation); | |
| //set the timeline aspects of the animation | |
| var tl = new TimelineMax({ | |
| repeat: -1, //-1 loop | |
| repeatDelay: 0.25, | |
| yoyo: true | |
| }); | |
| tl.fromTo( | |
| textAnimation, | |
| 4, //4 | |
| { animationProgress: 0.0 }, | |
| { animationProgress: 1.0, ease: Power1.easeInOut }, | |
| 0 | |
| ); | |
| createTweenScrubber(tl); | |
| } | |
| //create the text to be animated | |
| function createTextAnimation() { | |
| var geometry = generateTextGeometry("GONE WITH THE WIND", { | |
| size: 14, | |
| height: 0, | |
| font: "droid sans", | |
| weight: "bold", | |
| style: "normal", | |
| anchor: { x: 0.5, y: 0.5, z: 0.0 } | |
| }); | |
| THREE.BAS.Utils.separateFaces(geometry); | |
| return new TextAnimation(geometry); | |
| } | |
| //mathematical details of the animation | |
| function generateTextGeometry(text, params) { | |
| var geometry = new THREE.TextGeometry(text, params); | |
| geometry.computeBoundingBox(); | |
| geometry.userData = {}; | |
| geometry.userData.size = { | |
| width: geometry.boundingBox.max.x - geometry.boundingBox.min.x, | |
| height: geometry.boundingBox.max.y - geometry.boundingBox.min.y, | |
| depth: geometry.boundingBox.max.z - geometry.boundingBox.min.z | |
| }; | |
| var anchorX = geometry.userData.size.width * -params.anchor.x; | |
| var anchorY = geometry.userData.size.height * -params.anchor.y; | |
| var anchorZ = geometry.userData.size.depth * -params.anchor.z; | |
| var matrix = new THREE.Matrix4().makeTranslation(anchorX, anchorY, anchorZ); | |
| geometry.applyMatrix(matrix); | |
| return geometry; | |
| } | |
| //////////////////// | |
| // CLASSES | |
| //////////////////// | |
| function TextAnimation(textGeometry) { | |
| var bufferGeometry = new THREE.BAS.ModelBufferGeometry(textGeometry); | |
| var aAnimation = bufferGeometry.createAttribute("aAnimation", 2); | |
| var aCentroid = bufferGeometry.createAttribute("aCentroid", 3); | |
| var aControl0 = bufferGeometry.createAttribute("aControl0", 3); | |
| var aControl1 = bufferGeometry.createAttribute("aControl1", 3); | |
| var aEndPosition = bufferGeometry.createAttribute("aEndPosition", 3); | |
| var faceCount = bufferGeometry.faceCount; | |
| var i, i2, i3, i4, v; | |
| var size = textGeometry.userData.size; | |
| var maxDelayX = 2.0; | |
| var maxDelayY = 0.25; | |
| var minDuration = 2; | |
| var maxDuration = 8; | |
| var stretch = 0.25; | |
| this.animationDuration = maxDelayX + maxDelayY + maxDuration - 3; | |
| this._animationProgress = 0; | |
| for ( | |
| i = 0, i2 = 0, i3 = 0, i4 = 0; | |
| i < faceCount; | |
| i++, i2 += 6, i3 += 9, i4 += 12 | |
| ) { | |
| var face = textGeometry.faces[i]; | |
| var centroid = THREE.BAS.Utils.computeCentroid(textGeometry, face); | |
| // animation | |
| var delayX = Math.max(0, (centroid.x / size.width) * maxDelayX); | |
| var delayY = Math.max(0, (1.0 - centroid.y / size.height) * maxDelayY); | |
| var duration = THREE.Math.randFloat(minDuration, maxDuration); | |
| for (v = 0; v < 6; v += 2) { | |
| aAnimation.array[i2 + v] = delayX + delayY + Math.random() * stretch; | |
| aAnimation.array[i2 + v + 1] = duration; | |
| } | |
| // centroid | |
| for (v = 0; v < 9; v += 3) { | |
| aCentroid.array[i3 + v] = centroid.x; | |
| aCentroid.array[i3 + v + 1] = centroid.y; | |
| aCentroid.array[i3 + v + 2] = centroid.z; | |
| } | |
| // ctrl | |
| var c0x = centroid.x + THREE.Math.randFloat(40, 120); | |
| var c0y = centroid.y + size.height * THREE.Math.randFloat(0.0, 12.0); | |
| var c0z = THREE.Math.randFloatSpread(120); | |
| var c1x = centroid.x + THREE.Math.randFloat(80, 120) * -1; | |
| var c1y = centroid.y + size.height * THREE.Math.randFloat(0.0, 12.0); | |
| var c1z = THREE.Math.randFloatSpread(120); | |
| for (v = 0; v < 9; v += 3) { | |
| aControl0.array[i3 + v] = c0x; | |
| aControl0.array[i3 + v + 1] = c0y; | |
| aControl0.array[i3 + v + 2] = c0z; | |
| aControl1.array[i3 + v] = c1x; | |
| aControl1.array[i3 + v + 1] = c1y; | |
| aControl1.array[i3 + v + 2] = c1z; | |
| } | |
| // end position | |
| var x, y, z; | |
| x = centroid.x + THREE.Math.randFloatSpread(120); | |
| y = centroid.y + size.height * THREE.Math.randFloat(0.0, 12.0); | |
| z = THREE.Math.randFloat(-20, 20); | |
| for (v = 0; v < 9; v += 3) { | |
| aEndPosition.array[i3 + v] = x; | |
| aEndPosition.array[i3 + v + 1] = y; | |
| aEndPosition.array[i3 + v + 2] = z; | |
| } | |
| } | |
| var material = new THREE.BAS.BasicAnimationMaterial( | |
| { | |
| shading: THREE.FlatShading, | |
| side: THREE.DoubleSide, | |
| transparent: true, | |
| uniforms: { | |
| uTime: { type: "f", value: 0 } | |
| }, | |
| shaderFunctions: [ | |
| THREE.BAS.ShaderChunk["cubic_bezier"], | |
| THREE.BAS.ShaderChunk["ease_out_cubic"] | |
| ], | |
| shaderParameters: [ | |
| "uniform float uTime;", | |
| "attribute vec2 aAnimation;", | |
| "attribute vec3 aCentroid;", | |
| "attribute vec3 aControl0;", | |
| "attribute vec3 aControl1;", | |
| "attribute vec3 aEndPosition;" | |
| ], | |
| shaderVertexInit: [ | |
| "float tDelay = aAnimation.x;", | |
| "float tDuration = aAnimation.y;", | |
| "float tTime = clamp(uTime - tDelay, 0.0, tDuration);", | |
| "float tProgress = ease(tTime, 0.0, 1.0, tDuration);" | |
| //'float tProgress = tTime / tDuration;' | |
| ], | |
| shaderTransformPosition: [ | |
| "vec3 tPosition = transformed - aCentroid;", | |
| "tPosition *= 1.0 - tProgress;", | |
| "tPosition += aCentroid;", | |
| "tPosition += cubicBezier(tPosition, aControl0, aControl1, aEndPosition, tProgress);", | |
| "transformed = tPosition;" | |
| // 'vec3 tPosition = transformed;', | |
| // 'tPosition *= 1.0 - tProgress;', | |
| // 'tPosition += cubicBezier(transformed, aControl0, aControl1, aEndPosition, tProgress);', | |
| // 'tPosition += mix(transformed, aEndPosition, tProgress);', | |
| // 'transformed = tPosition;' | |
| ] | |
| }, | |
| { | |
| diffuse: 0xffffff //0000 | |
| } | |
| ); | |
| THREE.Mesh.call(this, bufferGeometry, material); | |
| this.frustumCulled = false; | |
| } | |
| TextAnimation.prototype = Object.create(THREE.Mesh.prototype); | |
| TextAnimation.prototype.constructor = TextAnimation; | |
| Object.defineProperty(TextAnimation.prototype, "animationProgress", { | |
| get: function () { | |
| return this._animationProgress; | |
| }, | |
| set: function (v) { | |
| this._animationProgress = v; | |
| this.material.uniforms["uTime"].value = this.animationDuration * v; | |
| } | |
| }); | |
| function THREERoot(params) { | |
| params = utils.extend( | |
| { | |
| antialias: false, | |
| fov: 60, | |
| zNear: 1, | |
| zFar: 10000, | |
| createCameraControls: true | |
| }, | |
| params | |
| ); | |
| this.renderer = new THREE.WebGLRenderer({ | |
| antialias: params.antialias | |
| }); | |
| document | |
| .getElementById("three-container") | |
| .appendChild(this.renderer.domElement); | |
| this.camera = new THREE.PerspectiveCamera( | |
| params.fov, | |
| window.innerWidth / window.innerHeight, | |
| params.zNear, | |
| params.zfar | |
| ); | |
| this.scene = new THREE.Scene(); | |
| if (params.createCameraControls) { | |
| this.controls = new THREE.OrbitControls( | |
| this.camera, | |
| this.renderer.domElement | |
| ); | |
| } | |
| this.resize = this.resize.bind(this); | |
| this.tick = this.tick.bind(this); | |
| this.resize(); | |
| this.tick(); | |
| window.addEventListener("resize", this.resize, false); | |
| } | |
| THREERoot.prototype = { | |
| tick: function () { | |
| this.update(); | |
| this.render(); | |
| requestAnimationFrame(this.tick); | |
| }, | |
| update: function () { | |
| this.controls && this.controls.update(); | |
| }, | |
| render: function () { | |
| this.renderer.render(this.scene, this.camera); | |
| }, | |
| resize: function () { | |
| this.camera.aspect = window.innerWidth / window.innerHeight; | |
| this.camera.updateProjectionMatrix(); | |
| this.renderer.setSize(window.innerWidth, window.innerHeight); | |
| } | |
| }; | |
| //////////////////// | |
| // UTILS | |
| //////////////////// | |
| var utils = { | |
| extend: function (dst, src) { | |
| for (var key in src) { | |
| dst[key] = src[key]; | |
| } | |
| return dst; | |
| }, | |
| randSign: function () { | |
| return Math.random() > 0.5 ? 1 : -1; | |
| } | |
| }; | |
| function createTweenScrubber(tween, seekSpeed) { | |
| seekSpeed = seekSpeed || 0.001; | |
| function stop() { | |
| TweenMax.to(tween, 2, { timeScale: 0 }); | |
| } | |
| function resume() { | |
| TweenMax.to(tween, 2, { timeScale: 1 }); | |
| } | |
| function seek(dx) { | |
| var progress = tween.progress(); | |
| var p = THREE.Math.clamp(progress + dx * seekSpeed, 0, 1); | |
| tween.progress(p); | |
| } | |
| var _cx = 0; | |
| // desktop | |
| var mouseDown = false; | |
| document.body.style.cursor = "pointer"; | |
| window.addEventListener("mousedown", function (e) { | |
| mouseDown = true; | |
| document.body.style.cursor = "ew-resize"; | |
| _cx = e.clientX; | |
| stop(); | |
| }); | |
| window.addEventListener("mouseup", function (e) { | |
| mouseDown = false; | |
| document.body.style.cursor = "pointer"; | |
| resume(); | |
| }); | |
| window.addEventListener("mousemove", function (e) { | |
| if (mouseDown === true) { | |
| var cx = e.clientX; | |
| var dx = cx - _cx; | |
| _cx = cx; | |
| seek(dx); | |
| } | |
| }); | |
| // mobile | |
| window.addEventListener("touchstart", function (e) { | |
| _cx = e.touches[0].clientX; | |
| stop(); | |
| e.preventDefault(); | |
| }); | |
| window.addEventListener("touchend", function (e) { | |
| resume(); | |
| e.preventDefault(); | |
| }); | |
| window.addEventListener("touchmove", function (e) { | |
| var cx = e.touches[0].clientX; | |
| var dx = cx - _cx; | |
| _cx = cx; | |
| seek(dx); | |
| e.preventDefault(); | |
| }); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| body { | |
| margin: 0; | |
| overflow: hidden; | |
| } | |
| #instructions { | |
| position: absolute; | |
| color: #fff; | |
| bottom: 0; | |
| padding-bottom: 6px; | |
| font-family: sans-serif; | |
| width: 100%; | |
| text-align: center; | |
| pointer-events: none; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment