一个《跳动的爱心》代码,纯HTML+JS,双击直接运行

  • 一个《跳动的爱心》代码,纯HTML+JS,双击直接运行已关闭评论
  • 215 次浏览
  • A+
所属分类:Web前端
摘要

HTML+JS实现的一个跳动的爱心。集合了web动画库GSAP JS、OBJ 文件加载器OBJLoader、WebGL第三方库Three.js等。效果非常棒!

HTML+JS实现的一个跳动的爱心。集合了web动画库GSAP JS、OBJ 文件加载器OBJLoader、WebGL第三方库Three.js等。效果非常棒!

实际效果:

一个《跳动的爱心》代码,纯HTML+JS,双击直接运行

由于是纯前端项目,JS代码没有任何加密,所以赶快给心爱的人,做一个跳动的爱心吧!

目录结构:

一个《跳动的爱心》代码,纯HTML+JS,双击直接运行

HTML代码

 <!DOCTYPE html> <html lang="en">  <head>   <meta charset="UTF-8">   <title>爱心</title>   <link rel="stylesheet" href="./css/style.css"> </head>  <body>    <script src='./js/three.min.js'></script>   <script src='./js/TrackballControls.js'></script>   <script src='./js/simplex-noise.js'></script>   <script src='./js/OBJLoader.js'></script>   <script src='./js/gsap.min.js'></script>   <script src="./js/script.js"></script>     <div id="main"></div>   <script type="text/javascript">     //获取父容器     var mainObj = document.getElementById('main')     //获取浏览器的高度     var innerWidth = document.body.clientWidth     var innerHeight = document.body.clientHeight          //计数器     var number = 0          /**      * 位置随机生成      */     var interval = setInterval(function() {         var heart = document.createElement('heart')         heart.style.left = Math.floor(Math.random() * innerWidth) + 'px'         heart.style.top = Math.floor(Math.random() * innerHeight) + 'px'         mainObj.appendChild(heart)         number++         //数量达到520时结束         if (number >= 520) {             clearInterval(interval)         }     }, 50)   </script>    <script>       (function () {       const _face = new THREE.Triangle();        const _color = new THREE.Vector3();        class MeshSurfaceSampler {          constructor(mesh) {            let geometry = mesh.geometry;            if (!geometry.isBufferGeometry || geometry.attributes.position.itemSize !== 3) {              throw new Error('THREE.MeshSurfaceSampler: Requires BufferGeometry triangle mesh.');            }            if (geometry.index) {              console.warn('THREE.MeshSurfaceSampler: Converting geometry to non-indexed BufferGeometry.');             geometry = geometry.toNonIndexed();            }            this.geometry = geometry;           this.randomFunction = Math.random;           this.positionAttribute = this.geometry.getAttribute('position');           this.colorAttribute = this.geometry.getAttribute('color');           this.weightAttribute = null;           this.distribution = null;          }          setWeightAttribute(name) {            this.weightAttribute = name ? this.geometry.getAttribute(name) : null;           return this;          }          build() {            const positionAttribute = this.positionAttribute;           const weightAttribute = this.weightAttribute;           const faceWeights = new Float32Array(positionAttribute.count / 3); // Accumulate weights for each mesh face.            for (let i = 0; i < positionAttribute.count; i += 3) {              let faceWeight = 1;              if (weightAttribute) {                faceWeight = weightAttribute.getX(i) + weightAttribute.getX(i + 1) + weightAttribute.getX(i + 2);              }              _face.a.fromBufferAttribute(positionAttribute, i);              _face.b.fromBufferAttribute(positionAttribute, i + 1);              _face.c.fromBufferAttribute(positionAttribute, i + 2);              faceWeight *= _face.getArea();             faceWeights[i / 3] = faceWeight;            } // Store cumulative total face weights in an array, where weight index           // corresponds to face index.             this.distribution = new Float32Array(positionAttribute.count / 3);           let cumulativeTotal = 0;            for (let i = 0; i < faceWeights.length; i++) {              cumulativeTotal += faceWeights[i];             this.distribution[i] = cumulativeTotal;            }            return this;          }          setRandomGenerator(randomFunction) {            this.randomFunction = randomFunction;           return this;          }          sample(targetPosition, targetNormal, targetColor) {            const cumulativeTotal = this.distribution[this.distribution.length - 1];           const faceIndex = this.binarySearch(this.randomFunction() * cumulativeTotal);           return this.sampleFace(faceIndex, targetPosition, targetNormal, targetColor);          }          binarySearch(x) {            const dist = this.distribution;           let start = 0;           let end = dist.length - 1;           let index = - 1;            while (start <= end) {              const mid = Math.ceil((start + end) / 2);              if (mid === 0 || dist[mid - 1] <= x && dist[mid] > x) {                index = mid;               break;              } else if (x < dist[mid]) {                end = mid - 1;              } else {                start = mid + 1;              }            }            return index;          }          sampleFace(faceIndex, targetPosition, targetNormal, targetColor) {            let u = this.randomFunction();           let v = this.randomFunction();            if (u + v > 1) {              u = 1 - u;             v = 1 - v;            }            _face.a.fromBufferAttribute(this.positionAttribute, faceIndex * 3);            _face.b.fromBufferAttribute(this.positionAttribute, faceIndex * 3 + 1);            _face.c.fromBufferAttribute(this.positionAttribute, faceIndex * 3 + 2);            targetPosition.set(0, 0, 0).addScaledVector(_face.a, u).addScaledVector(_face.b, v).addScaledVector(_face.c, 1 - (u + v));            if (targetNormal !== undefined) {              _face.getNormal(targetNormal);            }            if (targetColor !== undefined && this.colorAttribute !== undefined) {              _face.a.fromBufferAttribute(this.colorAttribute, faceIndex * 3);              _face.b.fromBufferAttribute(this.colorAttribute, faceIndex * 3 + 1);              _face.c.fromBufferAttribute(this.colorAttribute, faceIndex * 3 + 2);              _color.set(0, 0, 0).addScaledVector(_face.a, u).addScaledVector(_face.b, v).addScaledVector(_face.c, 1 - (u + v));              targetColor.r = _color.x;             targetColor.g = _color.y;             targetColor.b = _color.z;           }           return this;          }        }        THREE.MeshSurfaceSampler = MeshSurfaceSampler;      })();    </script>   <script>     (function () {        const _object_pattern = /^[og]s*(.+)?/; // mtllib file_reference        const _material_library_pattern = /^mtllib /; // usemtl material_name        const _material_use_pattern = /^usemtl /; // usemap map_name        const _map_use_pattern = /^usemap /;        const _vA = new THREE.Vector3();        const _vB = new THREE.Vector3();        const _vC = new THREE.Vector3();        const _ab = new THREE.Vector3();        const _cb = new THREE.Vector3();        function ParserState() {          const state = {           objects: [],           object: {},           vertices: [],           normals: [],           colors: [],           uvs: [],           materials: {},           materialLibraries: [],           startObject: function (name, fromDeclaration) {              // If the current object (initial from reset) is not from a g/o declaration in the parsed             // file. We need to use it for the first parsed g/o to keep things in sync.             if (this.object && this.object.fromDeclaration === false) {                this.object.name = name;               this.object.fromDeclaration = fromDeclaration !== false;               return;              }              const previousMaterial = this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined;              if (this.object && typeof this.object._finalize === 'function') {                this.object._finalize(true);              }              this.object = {               name: name || '',               fromDeclaration: fromDeclaration !== false,               geometry: {                 vertices: [],                 normals: [],                 colors: [],                 uvs: [],                 hasUVIndices: false               },               materials: [],               smooth: true,               startMaterial: function (name, libraries) {                  const previous = this._finalize(false); // New usemtl declaration overwrites an inherited material, except if faces were declared                 // after the material, then it must be preserved for proper MultiMaterial continuation.                   if (previous && (previous.inherited || previous.groupCount <= 0)) {                    this.materials.splice(previous.index, 1);                  }                  const material = {                   index: this.materials.length,                   name: name || '',                   mtllib: Array.isArray(libraries) && libraries.length > 0 ? libraries[libraries.length - 1] : '',                   smooth: previous !== undefined ? previous.smooth : this.smooth,                   groupStart: previous !== undefined ? previous.groupEnd : 0,                   groupEnd: - 1,                   groupCount: - 1,                   inherited: false,                   clone: function (index) {                      const cloned = {                       index: typeof index === 'number' ? index : this.index,                       name: this.name,                       mtllib: this.mtllib,                       smooth: this.smooth,                       groupStart: 0,                       groupEnd: - 1,                       groupCount: - 1,                       inherited: false                     };                     cloned.clone = this.clone.bind(cloned);                     return cloned;                    }                 };                 this.materials.push(material);                 return material;                },               currentMaterial: function () {                  if (this.materials.length > 0) {                    return this.materials[this.materials.length - 1];                  }                  return undefined;                },               _finalize: function (end) {                  const lastMultiMaterial = this.currentMaterial();                  if (lastMultiMaterial && lastMultiMaterial.groupEnd === - 1) {                    lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3;                   lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart;                   lastMultiMaterial.inherited = false;                  } // Ignore objects tail materials if no face declarations followed them before a new o/g started.                   if (end && this.materials.length > 1) {                    for (let mi = this.materials.length - 1; mi >= 0; mi--) {                      if (this.materials[mi].groupCount <= 0) {                        this.materials.splice(mi, 1);                      }                    }                  } // Guarantee at least one empty material, this makes the creation later more straight forward.                   if (end && this.materials.length === 0) {                    this.materials.push({                     name: '',                     smooth: this.smooth                   });                  }                  return lastMultiMaterial;                }             }; // Inherit previous objects material.             // Spec tells us that a declared material must be set to all objects until a new material is declared.             // If a usemtl declaration is encountered while this new object is being parsed, it will             // overwrite the inherited material. Exception being that there was already face declarations             // to the inherited material, then it will be preserved for proper MultiMaterial continuation.              if (previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function') {                const declared = previousMaterial.clone(0);               declared.inherited = true;               this.object.materials.push(declared);              }              this.objects.push(this.object);            },           finalize: function () {              if (this.object && typeof this.object._finalize === 'function') {                this.object._finalize(true);              }            },           parseVertexIndex: function (value, len) {              const index = parseInt(value, 10);             return (index >= 0 ? index - 1 : index + len / 3) * 3;            },           parseNormalIndex: function (value, len) {              const index = parseInt(value, 10);             return (index >= 0 ? index - 1 : index + len / 3) * 3;            },           parseUVIndex: function (value, len) {              const index = parseInt(value, 10);             return (index >= 0 ? index - 1 : index + len / 2) * 2;            },           addVertex: function (a, b, c) {              const src = this.vertices;             const dst = this.object.geometry.vertices;             dst.push(src[a + 0], src[a + 1], src[a + 2]);             dst.push(src[b + 0], src[b + 1], src[b + 2]);             dst.push(src[c + 0], src[c + 1], src[c + 2]);            },           addVertexPoint: function (a) {              const src = this.vertices;             const dst = this.object.geometry.vertices;             dst.push(src[a + 0], src[a + 1], src[a + 2]);            },           addVertexLine: function (a) {              const src = this.vertices;             const dst = this.object.geometry.vertices;             dst.push(src[a + 0], src[a + 1], src[a + 2]);            },           addNormal: function (a, b, c) {              const src = this.normals;             const dst = this.object.geometry.normals;             dst.push(src[a + 0], src[a + 1], src[a + 2]);             dst.push(src[b + 0], src[b + 1], src[b + 2]);             dst.push(src[c + 0], src[c + 1], src[c + 2]);            },           addFaceNormal: function (a, b, c) {              const src = this.vertices;             const dst = this.object.geometry.normals;              _vA.fromArray(src, a);              _vB.fromArray(src, b);              _vC.fromArray(src, c);              _cb.subVectors(_vC, _vB);              _ab.subVectors(_vA, _vB);              _cb.cross(_ab);              _cb.normalize();              dst.push(_cb.x, _cb.y, _cb.z);             dst.push(_cb.x, _cb.y, _cb.z);             dst.push(_cb.x, _cb.y, _cb.z);            },           addColor: function (a, b, c) {              const src = this.colors;             const dst = this.object.geometry.colors;             if (src[a] !== undefined) dst.push(src[a + 0], src[a + 1], src[a + 2]);             if (src[b] !== undefined) dst.push(src[b + 0], src[b + 1], src[b + 2]);             if (src[c] !== undefined) dst.push(src[c + 0], src[c + 1], src[c + 2]);            },           addUV: function (a, b, c) {              const src = this.uvs;             const dst = this.object.geometry.uvs;             dst.push(src[a + 0], src[a + 1]);             dst.push(src[b + 0], src[b + 1]);             dst.push(src[c + 0], src[c + 1]);            },           addDefaultUV: function () {              const dst = this.object.geometry.uvs;             dst.push(0, 0);             dst.push(0, 0);             dst.push(0, 0);            },           addUVLine: function (a) {              const src = this.uvs;             const dst = this.object.geometry.uvs;             dst.push(src[a + 0], src[a + 1]);            },           addFace: function (a, b, c, ua, ub, uc, na, nb, nc) {              const vLen = this.vertices.length;             let ia = this.parseVertexIndex(a, vLen);             let ib = this.parseVertexIndex(b, vLen);             let ic = this.parseVertexIndex(c, vLen);             this.addVertex(ia, ib, ic);             this.addColor(ia, ib, ic); // normals              if (na !== undefined && na !== '') {                const nLen = this.normals.length;               ia = this.parseNormalIndex(na, nLen);               ib = this.parseNormalIndex(nb, nLen);               ic = this.parseNormalIndex(nc, nLen);               this.addNormal(ia, ib, ic);              } else {                this.addFaceNormal(ia, ib, ic);              } // uvs               if (ua !== undefined && ua !== '') {                const uvLen = this.uvs.length;               ia = this.parseUVIndex(ua, uvLen);               ib = this.parseUVIndex(ub, uvLen);               ic = this.parseUVIndex(uc, uvLen);               this.addUV(ia, ib, ic);               this.object.geometry.hasUVIndices = true;              } else {                // add placeholder values (for inconsistent face definitions)               this.addDefaultUV();              }            },           addPointGeometry: function (vertices) {              this.object.geometry.type = 'Points';             const vLen = this.vertices.length;              for (let vi = 0, l = vertices.length; vi < l; vi++) {                const index = this.parseVertexIndex(vertices[vi], vLen);               this.addVertexPoint(index);               this.addColor(index);              }            },           addLineGeometry: function (vertices, uvs) {              this.object.geometry.type = 'Line';             const vLen = this.vertices.length;             const uvLen = this.uvs.length;              for (let vi = 0, l = vertices.length; vi < l; vi++) {                this.addVertexLine(this.parseVertexIndex(vertices[vi], vLen));              }              for (let uvi = 0, l = uvs.length; uvi < l; uvi++) {                this.addUVLine(this.parseUVIndex(uvs[uvi], uvLen));              }            }         };         state.startObject('', false);         return state;        } //         class OBJLoader extends THREE.Loader {          constructor(manager) {            super(manager);           this.materials = null;          }          load(url, onLoad, onProgress, onError) {            const scope = this;           const loader = new THREE.FileLoader(this.manager);           loader.setPath(this.path);           loader.setRequestHeader(this.requestHeader);           loader.setWithCredentials(this.withCredentials);           loader.load(url, function (text) {              try {                onLoad(scope.parse(text));              } catch (e) {                if (onError) {                  onError(e);                } else {                  console.error(e);                }                scope.manager.itemError(url);              }            }, onProgress, onError);          }          setMaterials(materials) {            this.materials = materials;           return this;          }          parse(text) {            const state = new ParserState();            if (text.indexOf('rn') !== - 1) {              // This is faster than String.split with regex that splits on both             text = text.replace(/rn/g, 'n');            }            if (text.indexOf('\n') !== - 1) {              // join lines separated by a line continuation character ()             text = text.replace(/\n/g, '');            }            const lines = text.split('n');           let line = '',             lineFirstChar = '';           let lineLength = 0;           let result = []; // Faster to just trim left side of the line. Use if available.            const trimLeft = typeof ''.trimLeft === 'function';            for (let i = 0, l = lines.length; i < l; i++) {              line = lines[i];             line = trimLeft ? line.trimLeft() : line.trim();             lineLength = line.length;             if (lineLength === 0) continue;             lineFirstChar = line.charAt(0); // @todo invoke passed in handler if any              if (lineFirstChar === '#') continue;              if (lineFirstChar === 'v') {                const data = line.split(/s+/);                switch (data[0]) {                  case 'v':                   state.vertices.push(parseFloat(data[1]), parseFloat(data[2]), parseFloat(data[3]));                    if (data.length >= 7) {                      state.colors.push(parseFloat(data[4]), parseFloat(data[5]), parseFloat(data[6]));                    } else {                      // if no colors are defined, add placeholders so color and vertex indices match                     state.colors.push(undefined, undefined, undefined);                    }                    break;                  case 'vn':                   state.normals.push(parseFloat(data[1]), parseFloat(data[2]), parseFloat(data[3]));                   break;                  case 'vt':                   state.uvs.push(parseFloat(data[1]), parseFloat(data[2]));                   break;                }              } else if (lineFirstChar === 'f') {                const lineData = line.substr(1).trim();               const vertexData = lineData.split(/s+/);               const faceVertices = []; // Parse the face vertex data into an easy to work with format                for (let j = 0, jl = vertexData.length; j < jl; j++) {                  const vertex = vertexData[j];                  if (vertex.length > 0) {                    const vertexParts = vertex.split('/');                   faceVertices.push(vertexParts);                  }                } // Draw an edge between the first vertex and all subsequent vertices to form an n-gon                 const v1 = faceVertices[0];                for (let j = 1, jl = faceVertices.length - 1; j < jl; j++) {                  const v2 = faceVertices[j];                 const v3 = faceVertices[j + 1];                 state.addFace(v1[0], v2[0], v3[0], v1[1], v2[1], v3[1], v1[2], v2[2], v3[2]);                }              } else if (lineFirstChar === 'l') {                const lineParts = line.substring(1).trim().split(' ');               let lineVertices = [];               const lineUVs = [];                if (line.indexOf('/') === - 1) {                  lineVertices = lineParts;                } else {                  for (let li = 0, llen = lineParts.length; li < llen; li++) {                    const parts = lineParts[li].split('/');                   if (parts[0] !== '') lineVertices.push(parts[0]);                   if (parts[1] !== '') lineUVs.push(parts[1]);                  }                }                state.addLineGeometry(lineVertices, lineUVs);              } else if (lineFirstChar === 'p') {                const lineData = line.substr(1).trim();               const pointData = lineData.split(' ');               state.addPointGeometry(pointData);              } else if ((result = _object_pattern.exec(line)) !== null) {                // o object_name               // or               // g group_name               // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869               // let name = result[ 0 ].substr( 1 ).trim();               const name = (' ' + result[0].substr(1).trim()).substr(1);               state.startObject(name);              } else if (_material_use_pattern.test(line)) {                // material               state.object.startMaterial(line.substring(7).trim(), state.materialLibraries);              } else if (_material_library_pattern.test(line)) {                // mtl file               state.materialLibraries.push(line.substring(7).trim());              } else if (_map_use_pattern.test(line)) {                // the line is parsed but ignored since the loader assumes textures are defined MTL files               // (according to https://www.okino.com/conv/imp_wave.htm, 'usemap' is the old-style Wavefront texture reference method)               console.warn('THREE.OBJLoader: Rendering identifier "usemap" not supported. Textures must be defined in MTL files.');              } else if (lineFirstChar === 's') {                result = line.split(' '); // smooth shading               // @todo Handle files that have varying smooth values for a set of faces inside one geometry,               // but does not define a usemtl for each face set.               // This should be detected and a dummy material created (later MultiMaterial and geometry groups).               // This requires some care to not create extra material on each smooth value for "normal" obj files.               // where explicit usemtl defines geometry groups.               // Example asset: examples/models/obj/cerberus/Cerberus.obj                /*                * http://paulbourke.net/dataformats/obj/                * or                * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf                *                * From chapter "Grouping" Syntax explanation "s group_number":                * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off.                * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form                * surfaces, smoothing groups are either turned on or off; there is no difference between values greater                * than 0."                */                if (result.length > 1) {                  const value = result[1].trim().toLowerCase();                 state.object.smooth = value !== '0' && value !== 'off';                } else {                  // ZBrush can produce "s" lines #11707                 state.object.smooth = true;                }                const material = state.object.currentMaterial();               if (material) material.smooth = state.object.smooth;              } else {                // Handle null terminated files without exception               if (line === '') continue;               console.warn('THREE.OBJLoader: Unexpected line: "' + line + '"');              }            }            state.finalize();           const container = new THREE.Group();           container.materialLibraries = [].concat(state.materialLibraries);           const hasPrimitives = !(state.objects.length === 1 && state.objects[0].geometry.vertices.length === 0);            if (hasPrimitives === true) {              for (let i = 0, l = state.objects.length; i < l; i++) {                const object = state.objects[i];               const geometry = object.geometry;               const materials = object.materials;               const isLine = geometry.type === 'Line';               const isPoints = geometry.type === 'Points';               let hasVertexColors = false; // Skip o/g line declarations that did not follow with any faces                if (geometry.vertices.length === 0) continue;               const buffergeometry = new THREE.BufferGeometry();               buffergeometry.setAttribute('position', new THREE.Float32BufferAttribute(geometry.vertices, 3));                if (geometry.normals.length > 0) {                  buffergeometry.setAttribute('normal', new THREE.Float32BufferAttribute(geometry.normals, 3));                }                if (geometry.colors.length > 0) {                  hasVertexColors = true;                 buffergeometry.setAttribute('color', new THREE.Float32BufferAttribute(geometry.colors, 3));                }                if (geometry.hasUVIndices === true) {                  buffergeometry.setAttribute('uv', new THREE.Float32BufferAttribute(geometry.uvs, 2));                } // Create materials                 const createdMaterials = [];                for (let mi = 0, miLen = materials.length; mi < miLen; mi++) {                  const sourceMaterial = materials[mi];                 const materialHash = sourceMaterial.name + '_' + sourceMaterial.smooth + '_' + hasVertexColors;                 let material = state.materials[materialHash];                  if (this.materials !== null) {                    material = this.materials.create(sourceMaterial.name); // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material.                    if (isLine && material && !(material instanceof THREE.LineBasicMaterial)) {                      const materialLine = new THREE.LineBasicMaterial();                     THREE.Material.prototype.copy.call(materialLine, material);                     materialLine.color.copy(material.color);                     material = materialLine;                    } else if (isPoints && material && !(material instanceof THREE.PointsMaterial)) {                      const materialPoints = new THREE.PointsMaterial({                       size: 10,                       sizeAttenuation: false                     });                     THREE.Material.prototype.copy.call(materialPoints, material);                     materialPoints.color.copy(material.color);                     materialPoints.map = material.map;                     material = materialPoints;                    }                  }                  if (material === undefined) {                    if (isLine) {                      material = new THREE.LineBasicMaterial();                    } else if (isPoints) {                      material = new THREE.PointsMaterial({                       size: 1,                       sizeAttenuation: false                     });                    } else {                      material = new THREE.MeshPhongMaterial();                    }                    material.name = sourceMaterial.name;                   material.flatShading = sourceMaterial.smooth ? false : true;                   material.vertexColors = hasVertexColors;                   state.materials[materialHash] = material;                  }                  createdMaterials.push(material);                } // Create mesh                 let mesh;                if (createdMaterials.length > 1) {                  for (let mi = 0, miLen = materials.length; mi < miLen; mi++) {                    const sourceMaterial = materials[mi];                   buffergeometry.addGroup(sourceMaterial.groupStart, sourceMaterial.groupCount, mi);                  }                  if (isLine) {                    mesh = new THREE.LineSegments(buffergeometry, createdMaterials);                  } else if (isPoints) {                    mesh = new THREE.Points(buffergeometry, createdMaterials);                  } else {                    mesh = new THREE.Mesh(buffergeometry, createdMaterials);                  }                } else {                  if (isLine) {                    mesh = new THREE.LineSegments(buffergeometry, createdMaterials[0]);                  } else if (isPoints) {                    mesh = new THREE.Points(buffergeometry, createdMaterials[0]);                  } else {                    mesh = new THREE.Mesh(buffergeometry, createdMaterials[0]);                  }                }                mesh.name = object.name;               container.add(mesh);              }            } else {              // if there is only the default parser state object with no geometry data, interpret data as point cloud             if (state.vertices.length > 0) {                const material = new THREE.PointsMaterial({                 size: 1,                 sizeAttenuation: false               });               const buffergeometry = new THREE.BufferGeometry();               buffergeometry.setAttribute('position', new THREE.Float32BufferAttribute(state.vertices, 3));                if (state.colors.length > 0 && state.colors[0] !== undefined) {                  buffergeometry.setAttribute('color', new THREE.Float32BufferAttribute(state.colors, 3));                 material.vertexColors = true;                }                const points = new THREE.Points(buffergeometry, material);               container.add(points);              }            }            return container;          }        }        THREE.OBJLoader = OBJLoader;      })();    </script>        </body>  </html>  

CSS代码

* {   padding: 0;   margin: 0; } body {   background: #ff5555;   overflow: hidden;   margin: 0;   /* background-color: #000 !important; */ } /** * 主容器 */ div#main {   width: 100vw;   height: 100vh; }  /** * 设置无限的动效 * 单次动效时间3s */ heart {   position: absolute;   width: 20px;   height: 20px;   color: #FFF;   text-align: center;   /* background: #e74c3c; */   font-size: 30px;   transform: rotate(360deg) scale(.6);   opacity: .5;   animation-name: opacity;   animation-duration: 3s;   animation-iteration-count: infinite; }  /** * 用伪类在heart  content即是展示的文字效果 */ heart::before {   position: absolute;   content: 'love-code';   width: 200px;   height: 20px;   /* background: #e74c3c; */   border-radius: 50%;   transform: translateX(-10px); }  /** *用伪类在heart */  heart::after {   position: absolute;   content: '';   width: 20px;   height: 20px;   /* background: #e74c3c; */   border-radius: 50%;   transform: translateY(-10px); }  /** * 改变透明度 */ @keyframes opacity {   25%,   75% {       opacity: 1;   }   50%,   100% {       opacity: .5;   } }  

js代码:

gsap.min.jsOBJLoader.jssimplex-noise.jsthree.min.jsTrackballControls.js 这几个JS都是现成的。
script.js代码:

console.clear();  const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(   75,   window.innerWidth / window.innerHeight,   0.1,   1000 );  const renderer = new THREE.WebGLRenderer({   antialias: true }); renderer.setClearColor(0xff5555); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement);  camera.position.z = 1;  const controls = new THREE.TrackballControls(camera, renderer.domElement); controls.noPan = true; controls.maxDistance = 3; controls.minDistance = 0.7;  const group = new THREE.Group(); scene.add(group);  let heart = null; let sampler = null; let originHeart = null; //new THREE.OBJLoader().load('./obj/heart_2.obj',obj => { new THREE.OBJLoader().load('https://assets.codepen.io/127738/heart_2.obj',obj => {   heart = obj.children[0];   heart.geometry.rotateX(-Math.PI * 0.5);   heart.geometry.scale(0.04, 0.04, 0.04);   heart.geometry.translate(0, -0.4, 0);   group.add(heart);      heart.material = new THREE.MeshBasicMaterial({     color: 0xff5555       });   originHeart = Array.from(heart.geometry.attributes.position.array);   sampler = new THREE.MeshSurfaceSampler(heart).build();   init();   renderer.setAnimationLoop(render); });  let positions = []; const geometry = new THREE.BufferGeometry(); const material = new THREE.LineBasicMaterial({   color: 0xffffff }); const lines = new THREE.LineSegments(geometry, material); group.add(lines);  const simplex = new SimplexNoise(); const pos = new THREE.Vector3(); class Grass {   constructor () {     sampler.sample(pos);     this.pos = pos.clone();     this.scale = Math.random() * 0.01 + 0.001;     this.one = null;     this.two = null;   }   update (a) {     const noise = simplex.noise4D(this.pos.x*1.5, this.pos.y*1.5, this.pos.z*1.5, a * 0.0005) + 1;     this.one = this.pos.clone().multiplyScalar(1.01 + (noise * 0.15 * beat.a));     this.two = this.one.clone().add(this.one.clone().setLength(this.scale));   } }  let spikes = []; function init (a) {   positions = [];   for (let i = 0; i < 20000; i++) {     const g = new Grass();     spikes.push(g);   } }  const beat = { a: 0 }; gsap.timeline({   repeat: -1,   repeatDelay: 0.3 }).to(beat, {   a: 1.2,   duration: 0.6,   ease: 'power2.in' }).to(beat, {   a: 0.0,   duration: 0.6,   ease: 'power3.out' }); gsap.to(group.rotation, {   y: Math.PI * 2,   duration: 12,   ease: 'none',   repeat: -1 });  function render(a) {   positions = [];   spikes.forEach(g => {     g.update(a);     positions.push(g.one.x, g.one.y, g.one.z);     positions.push(g.two.x, g.two.y, g.two.z);   });   geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3));      const vs = heart.geometry.attributes.position.array;   for (let i = 0; i < vs.length; i+=3) {     const v = new THREE.Vector3(originHeart[i], originHeart[i+1], originHeart[i+2]);     const noise = simplex.noise4D(originHeart[i]*1.5, originHeart[i+1]*1.5, originHeart[i+2]*1.5, a * 0.0005) + 1;     v.multiplyScalar(1 + (noise * 0.15 * beat.a));     vs[i] = v.x;     vs[i+1] = v.y;     vs[i+2] = v.z;   }   heart.geometry.attributes.position.needsUpdate = true;      controls.update();   renderer.render(scene, camera); }  window.addEventListener("resize", onWindowResize, false); function onWindowResize() {   camera.aspect = window.innerWidth / window.innerHeight;   camera.updateProjectionMatrix();   renderer.setSize(window.innerWidth, window.innerHeight); } 

简单的修改

  • 页面闪烁的love-code一共展现520个。可在index.html调整,大约在42行处
  • love-code可以换成你想要的,可在css/style.css里修改,大约在43行处
  • 爱心是读取的网络的3dmax文件,你也可以修改为自己的3dmax文件,位置是js/script.js文件,大约在32行处。

完整文件下载

下载方式:
扫描