Let’s create an awesome Anime.js 3D Logo Animation using HTML, CSS, and JavaScript. In this project, we’ll build a smooth 3D logo animation where a cube falls, the floor shakes and breaks apart, and the logo comes together with cool camera movements and effects.
We’ll use:
- HTML: To create the page structure and add the SVG logo.
- CSS: To style the page with a dark background, make the canvas full screen, and hide the original SVG.
- JavaScript: To convert the SVG into a 3D logo, create the scene, camera, lights, and animations using Three.js and Anime.js. It also controls the cube animation, floor effects, logo reveal, and smooth camera transitions.
This project is perfect for learning Three.js, Anime.js, SVG to 3D conversion, animations, camera movement, and creating impressive 3D web animations.
HTML :
The HTML file sets up the webpage by importing Anime.js and Three.js libraries, includes an SVG logo that will be converted into a 3D model, and links the external CSS and JavaScript files. It also provides the basic page structure where the animation is rendered on a WebGL canvas.
<!DOCTYPE html>
<html lang="en">
<head>
<script type="importmap">
{
"imports": {
"animejs": "https://unpkg.com/animejs@4.5.0/dist/modules/index.js",
"animejs/": "https://esm.sh/animejs@^4.4.1/",
"animejs/adapters/three": "https://unpkg.com/animejs@4.5.0/dist/modules/adapters/three/index.js",
"three": "https://unpkg.com/three@0.184.0/build/three.module.js",
"three/": "https://esm.sh/three@^0.184.0/",
"three/addons/": "https://unpkg.com/three@0.184.0/examples/jsm/"
}
}
</script>
<meta charset="UTF-8">
<title>Anime.js 3D logo animation (Three.js adapter)</title>
<link rel="stylesheet" href="https://codepen.io/juliangarnier/pen/ByaMBKr/31455c4a32d8ef013c00f904985e6268">
<link rel="stylesheet" href="/style.css">
</head>
<body>
<svg id="logo" viewBox="0 0 7.156 4.024" xmlns="http://www.w3.org/2000/svg">
<g fill-rule="evenodd" transform="scale(0.01118)">
<polygon id="i-dot" fill="#FF4B4B" points="309.202438 224.68 309.293324 207 332.173324 207 332.212438 224.68" />
<polygon id="e-dot" fill="#FF4B4B" points="585.36 224.68 587.96 207 610.84 207 608.37 224.68" />
<path id="a" fill="#FFF"
d="M94.78,296.44 C88.1066667,296.44 82.8416667,295.963333 78.985,295.01 C75.1283333,294.056667 72.3333333,292.496667 70.6,290.33 C68.8666667,288.163333 68,285.216667 68,281.49 C68,279.843333 68.13,278.196667 68.39,276.55 L68.78,273.69 C69.4733333,268.836667 70.8166667,265.153333 72.81,262.64 C74.8033333,260.126667 77.9233333,258.35 82.17,257.31 C86.4166667,256.27 92.4833333,255.75 100.37,255.75 L102.97,255.75 C109.123333,255.75 113.933333,256.205 117.4,257.115 C120.866667,258.025 123.38,259.52 124.94,261.6 L125.33,261.6 L125.98,255.49 C126.153333,253.93 126.24,252.933333 126.24,252.5 C126.24,250.42 125.806667,248.903333 124.94,247.95 C124.073333,246.996667 122.751667,246.368333 120.975,246.065 C119.198333,245.761667 116.576667,245.61 113.11,245.61 L110.51,245.61 C106.436667,245.61 103.338333,245.805 101.215,246.195 C99.0916667,246.585 97.6183333,247.191667 96.795,248.015 C95.9716667,248.838333 95.43,249.986667 95.17,251.46 L95.04,251.85 L73.46,251.85 L73.72,249.9 C74.4133333,244.96 75.995,241.233333 78.465,238.72 C80.935,236.206667 84.835,234.451667 90.165,233.455 C95.495,232.458333 103.143333,231.96 113.11,231.96 L116.23,231.96 C124.463333,231.96 130.855,232.501667 135.405,233.585 C139.955,234.668333 143.291667,236.64 145.415,239.5 C147.538333,242.36 148.6,246.563333 148.6,252.11 C148.6,254.363333 148.296667,257.786667 147.69,262.38 L143.01,295.4 L120.52,295.4 L121.69,288.38 L121.43,288.38 C119.61,290.46 117.833333,292.063333 116.1,293.19 C114.366667,294.316667 112.07,295.14 109.21,295.66 C106.35,296.18 102.45,296.44 97.51,296.44 L94.78,296.44 Z M106.87,282.53 C111.55,282.53 115.016667,282.335 117.27,281.945 C119.523333,281.555 121.083333,280.905 121.95,279.995 C122.816667,279.085 123.38,277.72 123.64,275.9 L123.9,274.86 C123.986667,274.426667 124.03,273.82 124.03,273.04 C124.03,271.826667 123.64,270.895 122.86,270.245 C122.08,269.595 120.628333,269.14 118.505,268.88 C116.381667,268.62 113.24,268.49 109.08,268.49 L107.65,268.49 C102.97,268.49 99.4816667,268.706667 97.185,269.14 C94.8883333,269.573333 93.3066667,270.245 92.44,271.155 C91.5733333,272.065 91.01,273.43 90.75,275.25 L90.62,276.29 L90.49,277.59 C90.49,278.89 90.9666667,279.908333 91.92,280.645 C92.8733333,281.381667 94.4116667,281.88 96.535,282.14 C98.6583333,282.4 101.67,282.53 105.57,282.53 L106.87,282.53 Z" />
<path id="n" fill="#FFF"
d="M159.1,233 L181.98,233 L180.42,242.75 L180.68,242.75 C182.586667,240.063333 184.688333,237.94 186.985,236.38 C189.281667,234.82 192.011667,233.693333 195.175,233 C198.338333,232.306667 202.216667,231.96 206.81,231.96 L207.98,231.96 C214.74,231.96 220.156667,232.61 224.23,233.91 C228.303333,235.21 231.315,237.42 233.265,240.54 C235.215,243.66 236.19,247.906667 236.19,253.28 C236.19,255.533333 235.973333,258.523333 235.54,262.25 L230.73,295.4 L207.85,295.4 L212.4,263.03 C212.66,261.21 212.79,259.65 212.79,258.35 C212.79,255.836667 212.313333,253.951667 211.36,252.695 C210.406667,251.438333 208.933333,250.593333 206.94,250.16 C204.946667,249.726667 202.173333,249.51 198.62,249.51 L197.19,249.51 C192.683333,249.51 189.086667,250.03 186.4,251.07 C183.713333,252.11 181.698333,253.691667 180.355,255.815 C179.011667,257.938333 178.08,260.776667 177.56,264.33 L173.14,295.4 L150.26,295.4 L159.1,233 Z" />
<polygon id="i-bar" fill="#FFF" points="237.98 295.4 246.82 233 269.7 233 260.86 295.4" />
<path id="m" fill="#FFF"
d="M277.34,233 L300.22,233 L298.66,242.75 L298.92,242.75 C301.26,239.37 304.336667,236.726667 308.15,234.82 C311.963333,232.913333 316.86,231.961 322.84,231.96 L324.4,231.96 C331.68,231.96 337.161667,233.021667 340.845,235.145 C344.528333,237.268333 346.933333,240.713333 348.06,245.48 L348.19,245.48 C350.616667,240.973333 354.018333,237.593333 358.395,235.34 C362.771667,233.086667 368.34,231.96 375.1,231.96 L376.53,231.96 C382.683333,231.96 387.558333,232.566667 391.155,233.78 C394.751667,234.993333 397.373333,237.03 399.02,239.89 C400.666667,242.75 401.49,246.693333 401.49,251.72 C401.49,254.06 401.186667,257.57 400.58,262.25 L395.9,295.4 L373.02,295.4 L377.7,262.9 C377.96,261.08 378.09,259.52 378.09,258.22 C378.09,255.88 377.678333,254.081667 376.855,252.825 C376.031667,251.568333 374.84,250.701667 373.28,250.225 C371.72,249.748333 369.64,249.51 367.04,249.51 L365.74,249.51 C361.32,249.51 357.918333,249.986667 355.535,250.94 C353.151667,251.893333 351.44,253.388333 350.4,255.425 C349.36,257.461667 348.58,260.343333 348.06,264.07 L343.64,295.4 L320.76,295.4 L325.31,262.9 C325.57,261.686667 325.7,260.17 325.7,258.35 C325.7,255.923333 325.288333,254.06 324.465,252.76 C323.641667,251.46 322.471667,250.593333 320.955,250.16 C319.438333,249.726667 317.423333,249.51 314.91,249.51 L313.61,249.51 C309.103333,249.51 305.68,249.986667 303.34,250.94 C301,251.893333 299.288333,253.366667 298.205,255.36 C297.121667,257.353333 296.32,260.3 295.8,264.2 L291.38,295.4 L268.5,295.4 L277.34,233 Z" />
<path id="e" fill="#FFF"
d="M440.29,296.44 C431.71,296.44 424.928333,295.833333 419.945,294.62 C414.961667,293.406667 411.256667,291.196667 408.83,287.99 C406.403333,284.783333 405.19,280.19 405.19,274.21 C405.19,270.83 405.406667,267.753333 405.84,264.98 L406.1,262.51 C407.313333,253.843333 409.393333,247.343333 412.34,243.01 C415.286667,238.676667 419.381667,235.751667 424.625,234.235 C429.868333,232.718333 437.17,231.96 446.53,231.96 L450.56,231.96 C458.62,231.96 465.098333,232.523333 469.995,233.65 C474.891667,234.776667 478.553333,236.813333 480.98,239.76 C483.406667,242.706667 484.62,246.823333 484.62,252.11 C484.62,254.623333 484.446667,256.963333 484.1,259.13 L482.8,268.88 L427.68,268.88 L427.42,270.96 C427.16,272.26 427.03,273.343333 427.03,274.21 C427.03,276.463333 427.571667,278.131667 428.655,279.215 C429.738333,280.298333 431.298333,281.013333 433.335,281.36 C435.371667,281.706667 438.34,281.88 442.24,281.88 L444.71,281.88 C448.956667,281.88 452.185,281.641667 454.395,281.165 C456.605,280.688333 458.165,279.951667 459.075,278.955 C459.985,277.958333 460.57,276.55 460.83,274.73 L460.83,274.47 L482.28,274.47 L482.02,276.42 C481.24,281.793333 479.441667,285.91 476.625,288.77 C473.808333,291.63 469.865,293.623333 464.795,294.75 C459.725,295.876667 453.03,296.44 444.71,296.44 L440.29,296.44 Z M462.91,257.05 L463.17,254.71 C463.343333,254.103333 463.43,253.236667 463.43,252.11 C463.43,250.376667 462.996667,249.055 462.13,248.145 C461.263333,247.235 459.855,246.585 457.905,246.195 C455.955,245.805 453.203333,245.61 449.65,245.61 L447.05,245.61 C442.456667,245.61 438.968333,245.956667 436.585,246.65 C434.201667,247.343333 432.49,248.47 431.45,250.03 C430.41,251.59 429.673333,253.93 429.24,257.05 L462.91,257.05 Z" />
<path id="j" fill="#FFF"
d="M477.57,317.5 C475.663333,317.5 474.103333,317.413333 472.89,317.24 L473.28,315.03 C474.666667,315.203333 476.183333,315.29 477.83,315.29 C479.91,315.29 481.535,314.878333 482.705,314.055 C483.875,313.231667 484.828333,311.78 485.565,309.7 C486.301667,307.62 486.973333,304.543333 487.58,300.47 L497.07,233 L499.8,233 L490.31,300.6 C489.703333,304.846667 488.923333,308.183333 487.97,310.61 C487.016667,313.036667 485.716667,314.791667 484.07,315.875 C482.423333,316.958333 480.256667,317.5 477.57,317.5 Z" />
<path id="s" fill="#FFF"
d="M534.26,296.31 C525.333333,296.31 518.616667,295.985 514.11,295.335 C509.603333,294.685 506.396667,293.45 504.49,291.63 C502.583333,289.81 501.63,286.993333 501.63,283.18 C501.63,281.966667 501.695,280.861667 501.825,279.865 C501.955,278.868333 502.106667,277.806667 502.28,276.68 L505.01,276.68 C504.576667,279.02 504.36,281.1 504.36,282.92 C504.36,286.04 505.205,288.358333 506.895,289.875 C508.585,291.391667 511.488333,292.431667 515.605,292.995 C519.721667,293.558333 525.94,293.84 534.26,293.84 L538.81,293.84 C546.436667,293.84 552.243333,293.363333 556.23,292.41 C560.216667,291.456667 563.033333,289.983333 564.68,287.99 C566.326667,285.996667 567.41,283.136667 567.93,279.41 C568.276667,277.243333 568.45,275.466667 568.45,274.08 C568.45,271.48 567.648333,269.508333 566.045,268.165 C564.441667,266.821667 561.581667,265.89 557.465,265.37 C553.348333,264.85 547.173333,264.546667 538.94,264.46 C530.533333,264.373333 524.141667,263.961667 519.765,263.225 C515.388333,262.488333 512.355,261.34 510.665,259.78 C508.975,258.22 508.13,256.01 508.13,253.15 C508.13,251.85 508.346667,249.856667 508.78,247.17 C509.3,243.183333 510.491667,240.128333 512.355,238.005 C514.218333,235.881667 517.316667,234.365 521.65,233.455 C525.983333,232.545 532.31,232.09 540.63,232.09 L543.88,232.09 C551.333333,232.09 557.096667,232.48 561.17,233.26 C565.243333,234.04 568.19,235.383333 570.01,237.29 C571.83,239.196667 572.74,241.926667 572.74,245.48 C572.74,246.866667 572.566667,248.903333 572.22,251.59 L569.62,251.59 C569.966667,249.163333 570.14,247.17 570.14,245.61 C570.14,242.663333 569.338333,240.41 567.735,238.85 C566.131667,237.29 563.466667,236.185 559.74,235.535 C556.013333,234.885 550.683333,234.56 543.75,234.56 L540.5,234.56 C532.7,234.56 526.828333,234.971667 522.885,235.795 C518.941667,236.618333 516.168333,237.918333 514.565,239.695 C512.961667,241.471667 511.9,244.136667 511.38,247.69 C511.033333,249.943333 510.86,251.633333 510.86,252.76 C510.86,255.186667 511.618333,257.028333 513.135,258.285 C514.651667,259.541667 517.403333,260.43 521.39,260.95 C525.376667,261.47 531.313333,261.773333 539.2,261.86 C547.866667,261.946667 554.431667,262.336667 558.895,263.03 C563.358333,263.723333 566.5,264.893333 568.32,266.54 C570.14,268.186667 571.05,270.613333 571.05,273.82 C571.05,275.206667 570.876667,277.156667 570.53,279.67 C569.923333,284.003333 568.645,287.34 566.695,289.68 C564.745,292.02 561.581667,293.71 557.205,294.75 C552.828333,295.79 546.653333,296.31 538.68,296.31 L534.26,296.31 Z" />
<g fill-rule="nonzero" transform="translate(-411.828, -39.3499) scale(1.371915)">
<path id="three" fill="#FF4B4B"
d="M783.738028,198.502816 C788.547202,198.998027 792.016591,199.887155 794.146194,201.170201 C796.275798,202.453247 797.410619,204.355307 797.550658,206.876379 C797.600672,207.776762 797.560688,208.767184 797.430705,209.847644 L797.304473,210.995632 C796.937845,213.516705 796.346208,215.40751 795.529561,216.668046 C794.712914,217.928582 793.482929,218.873985 791.839606,219.504253 C790.196282,220.134521 787.740457,220.674751 784.472131,221.124943 L784.479633,221.26 C787.157138,221.575134 789.286103,221.991561 790.866528,222.509282 C792.446952,223.027002 793.659255,223.758563 794.503437,224.703966 C795.347618,225.649368 795.813471,226.909904 795.900996,228.485575 C795.941007,229.205881 795.896021,230.106264 795.766038,231.186724 L795.578565,232.942471 C795.079454,236.498985 793.734911,239.087586 791.544937,240.708276 C789.354962,242.328966 785.725812,243.409425 780.657487,243.949655 C775.589162,244.489885 767.608333,244.76 756.715,244.76 L754.15,244.76 C746.233333,244.76 740.154162,244.489885 735.912487,243.949655 C731.670812,243.409425 728.575797,242.419004 726.627441,240.978391 C724.679085,239.537778 723.627385,237.421877 723.472342,234.63069 C723.397321,233.280115 723.504798,231.794483 723.794772,230.173793 L723.982245,228.418046 L740.892245,228.418046 L740.632279,230.578966 C740.588951,230.939119 740.583542,231.41182 740.616051,231.997069 C740.676068,233.077529 741.161913,233.842854 742.073587,234.293046 C742.98526,234.743238 744.362556,235.024607 746.205475,235.137155 C747.911881,235.241366 750.728718,235.297331 754.655987,235.305051 L757.139852,235.305977 C763.219852,235.305977 767.662767,235.193429 770.468597,234.968333 C773.274427,234.743238 775.182962,234.32681 776.194203,233.719052 C777.205443,233.111293 777.831886,232.132126 778.073532,230.781552 L778.214768,229.903678 C778.306425,229.27341 778.347252,228.868238 778.337249,228.688161 C778.289736,227.832797 777.938059,227.202529 777.28222,226.797356 C776.62638,226.392184 775.613876,226.122069 774.244707,225.987011 C772.875538,225.851954 770.860954,225.784425 768.200954,225.784425 L752.335954,225.784425 L753.638312,216.735575 L769.218312,216.735575 C771.624979,216.735575 773.518102,216.611772 774.897682,216.364167 C776.277261,216.116561 777.298504,215.688879 777.961412,215.081121 C778.624319,214.473362 779.052431,213.629253 779.245747,212.548793 L779.481983,211.67092 C779.530312,211.400805 779.541973,211.040651 779.516966,210.59046 C779.45945,209.555019 778.895063,208.800948 777.823806,208.328247 C776.752549,207.855546 775.009836,207.551667 772.595667,207.416609 C770.270912,207.286554 766.716329,207.219118 761.931919,207.214301 L759.949414,207.214023 C754.94608,207.214023 751.374624,207.337826 749.235044,207.585431 C747.095465,207.833036 745.633389,208.305738 744.848817,209.003534 C744.064244,209.701331 743.559888,210.883084 743.335747,212.548793 L743.122017,213.831839 L726.402017,213.831839 L726.760734,211.738448 C727.409834,207.461628 728.616661,204.389071 730.381215,202.520776 C732.14577,200.652481 735.194917,199.391944 739.528656,198.739167 C743.759212,198.101931 750.481532,197.775727 759.695618,197.760555 L762.844266,197.76 C771.964266,197.76 778.928853,198.007605 783.738028,198.502816 Z" />
<path id="d" fill="#FF4B4B"
d="M869.159588,199.313161 C874.200432,199.988448 877.883577,201.305259 880.209025,203.263592 C882.534472,205.221925 883.807226,208.181935 884.027288,212.143621 C884.162326,214.574655 883.996952,217.298314 883.531166,220.314598 L883.191204,222.745632 C882.231308,229.408467 880.690132,234.157989 878.567676,236.994195 C876.445221,239.830402 872.704001,241.743716 867.344016,242.734138 L866.684346,242.850611 C861.387898,243.744368 853.059224,244.200556 841.698321,244.219176 L798.979039,244.21977 L805.548322,198.30023 L847.063322,198.30023 C856.753322,198.30023 864.118744,198.637874 869.159588,199.313161 Z M845.161903,208.159947 L822.340976,208.159425 L818.651381,234.09046 L840.596381,234.09046 C847.689714,234.09046 852.80346,233.797835 855.937617,233.212586 C859.071775,232.627337 861.241757,231.501858 862.447565,229.836149 C863.653372,228.170441 864.531246,225.446782 865.081187,221.665172 L865.36366,219.909425 C865.641131,218.06364 865.743606,216.487969 865.671086,215.182414 C865.553553,213.066513 864.849778,211.513352 863.559763,210.522931 C862.269748,209.53251 860.223279,208.890987 857.420358,208.598362 L856.819323,208.539907 C854.123313,208.295647 850.237506,208.168994 845.161903,208.159947 Z" />
</g>
</g>
</svg>
<script type="module" src="/script.js"></script>
</body>
</html>
CSS :
The CSS removes the default page margins, applies a dark background, hides scrollbars, makes the canvas fill the entire screen, and hides the original SVG logo since it is only used as a source for generating the 3D model.
html,
body {
margin: 0;
padding: 0;
height: 100%;
background-color: #252423;
overflow: hidden;
}
canvas {
display: block;
}
#logo {
display: none;
}
JS :
The JavaScript is the core of the project. It converts the SVG logo into 3D objects using Three.js, creates the scene, camera, lights, floor, and sky, then uses Anime.js to animate everything. The animation includes a falling cube, floor impact, tile explosion, logo formation, camera movements, lighting effects, and a final burst animation before seamlessly looping.
import {
Scene, WebGLRenderer, PerspectiveCamera, Color, Group, Box3, Vector3,
Mesh, ExtrudeGeometry, MeshStandardMaterial,
InstancedMesh, BoxGeometry, Object3D,
AmbientLight, DirectionalLight,
ShaderMaterial, SphereGeometry, BackSide,
} from 'three';
import { SVGLoader } from 'three/addons/loaders/SVGLoader.js';
import { animate, createTimeline, createTimer, stagger, utils, cubicBezier, linear } from 'animejs';
import { getInstances } from 'animejs/adapters/three';
const LETTER_IDS = ['a', 'n', 'i-bar', 'm', 'e'];
const JS_IDS = ['j', 's'];
const THREED_IDS = ['three', 'd'];
const DEPTH = 0.2565;
const ITALIC_SKEW = 8;
// Parse the SVG logo into centered, extruded 3D geometries
const svgEl = document.querySelector('#logo');
const data = new SVGLoader().parse(svgEl.outerHTML);
svgEl.remove();
const parsed = {};
function flipShapeY(shape) {
const flipPath = (p) => p.curves.forEach((c) => {
if (c.v0) c.v0.y = -c.v0.y;
if (c.v1) c.v1.y = -c.v1.y;
if (c.v2) c.v2.y = -c.v2.y;
if (c.v3) c.v3.y = -c.v3.y;
});
flipPath(shape);
shape.holes.forEach(flipPath);
}
for (const path of data.paths) {
const id = path.userData?.node?.id;
if (!id) continue;
const shapes = SVGLoader.createShapes(path);
shapes.forEach(flipShapeY);
const geom = new ExtrudeGeometry(shapes, { depth: DEPTH, bevelEnabled: false });
const bbox = new Box3().setFromBufferAttribute(geom.attributes.position);
const center = bbox.getCenter(new Vector3());
const size = bbox.getSize(new Vector3());
geom.translate(-center.x, -center.y, -center.z);
parsed[id] = { geom, center, size, color: path.userData.style.fill || '#fff' };
}
// Align dot with the letter i so the dot sits directly above the i.
parsed['i-dot'].center.x = parsed['i-bar'].center.x;
const meshes = {};
const logoGroup = new Group();
for (const id of [...LETTER_IDS, 'i-dot', ...JS_IDS, ...THREED_IDS]) {
const { geom, center, size, color } = parsed[id];
const mesh = new Mesh(geom, new MeshStandardMaterial({
color: new Color(color), roughness: 0.4, metalness: 0.1,
}));
mesh.position.copy(center);
mesh.userData.halfHeight = size.y / 2;
meshes[id] = mesh;
logoGroup.add(mesh);
}
const FLOOR_COLS = 31;
const FLOOR_ROWS = 31;
const tileSize = 1;
const scale = tileSize / parsed['i-dot'].size.x;
logoGroup.scale.setScalar(scale);
const iLocalBottomY = parsed['i-bar'].center.y - parsed['i-bar'].size.y / 2;
logoGroup.position.set(
-parsed['i-dot'].center.x * scale,
tileSize / 2 - iLocalBottomY * scale,
0,
);
const wrapper = new Group();
wrapper.add(logoGroup);
// Land the logo pop at the floor instead of 5 above, the intro is untouched since the logo is hidden until POP.
wrapper.position.y = -5;
const SCENE_GREY = '#b5b5ba';
const HORIZON_GREY = '#cfcfd4';
const SKY_GREY = '#8e8e96';
const BG = '#252423';
// Scene and gradient sky dome
const scene = new Scene();
scene.background = new Color(BG);
// Vertical gradient skydome, colors and horizon position driven by uniforms so they stay editable.
const skyMaterial = new ShaderMaterial({
uniforms: {
topColor: { value: new Color(SKY_GREY) },
bottomColor: { value: new Color(HORIZON_GREY) },
offset: { value: 32 },
exponent: { value: 0.6 },
},
side: BackSide,
fog: false,
vertexShader: `
varying vec3 vWorldPosition;
void main() {
vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform vec3 topColor;
uniform vec3 bottomColor;
uniform float offset;
uniform float exponent;
varying vec3 vWorldPosition;
void main() {
float h = normalize(vWorldPosition + vec3(0.0, offset, 0.0)).y;
gl_FragColor = vec4(mix(bottomColor, topColor, pow(max(h, 0.0), exponent)), 1.0);
#include <colorspace_fragment>
}
`,
});
const sky = new Mesh(new SphereGeometry(600, 32, 15), skyMaterial);
scene.add(sky);
scene.add(wrapper);
// Floor: instanced tile grid with solid skirts
const floorGroup = new Group();
const floor = new InstancedMesh(
new BoxGeometry(tileSize, tileSize, tileSize),
new MeshStandardMaterial({ color: new Color(SCENE_GREY), roughness: 0.7, metalness: 0.05 }),
FLOOR_COLS * FLOOR_ROWS,
);
const dummy = new Object3D();
for (let r = 0; r < FLOOR_ROWS; r++) {
for (let c = 0; c < FLOOR_COLS; c++) {
dummy.position.set(c - (FLOOR_COLS - 1) / 2, 0, r - (FLOOR_ROWS - 1) / 2);
dummy.updateMatrix();
floor.setMatrixAt(r * FLOOR_COLS + c, dummy.matrix);
}
}
floorGroup.add(floor);
const tiles = getInstances(floor);
// Extend the floor on all four sides with solid skirts, centered on the origin like the tile grid, so the edges never show.
const FLOOR_HALF_X = FLOOR_COLS * tileSize / 2;
const FLOOR_HALF_Z = FLOOR_ROWS * tileSize / 2;
const SKIRT_EXT = 5;
const skirts = [];
const addSkirt = (width, depth, x, z) => {
const skirt = new Mesh(new BoxGeometry(width, tileSize, depth), floor.material);
skirt.position.set(x, 0, z);
floorGroup.add(skirt);
skirts.push(skirt);
};
addSkirt(SKIRT_EXT, 2 * FLOOR_HALF_Z, FLOOR_HALF_X + SKIRT_EXT / 2, 0);
addSkirt(SKIRT_EXT, 2 * FLOOR_HALF_Z, -FLOOR_HALF_X - SKIRT_EXT / 2, 0);
addSkirt(2 * FLOOR_HALF_X + 2 * SKIRT_EXT, SKIRT_EXT, 0, FLOOR_HALF_Z + SKIRT_EXT / 2);
addSkirt(2 * FLOOR_HALF_X + 2 * SKIRT_EXT, SKIRT_EXT, 0, -FLOOR_HALF_Z - SKIRT_EXT / 2);
scene.add(floorGroup);
const cube = new Mesh(
new BoxGeometry(tileSize, tileSize, tileSize),
new MeshStandardMaterial({ color: '#FF4B4B', roughness: 0.4, metalness: 0.1 }),
);
scene.add(cube);
// Lights, camera and renderer
const ambient = new AmbientLight(0xffffff, 0.6);
scene.add(ambient);
const keyLight = new DirectionalLight(0xffffff, 1.6);
keyLight.position.set(100, 200, 300);
scene.add(keyLight);
const rimLight = new DirectionalLight(0xaab8ff, 0.6);
rimLight.position.set(-200, -50, 150);
scene.add(rimLight);
const camera = new PerspectiveCamera(35, innerWidth / innerHeight, 0.01, 100);
const cameraRig = new Group();
cameraRig.add(camera);
scene.add(cameraRig);
const renderer = new WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(devicePixelRatio || 1);
document.body.appendChild(renderer.domElement);
renderer.domElement.style.opacity = '0';
addEventListener('resize', () => {
const aspect = innerWidth / innerHeight;
camera.aspect = aspect;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
// Initial pose for every animated target
const letters = LETTER_IDS.map(id => meshes[id]);
const dot = meshes['i-dot'];
const bottomOrigin = (m) => `0 ${-m.userData.halfHeight} 0`;
const dotRestX = dot.position.x;
letters.forEach(m => utils.set(m, {
skewX: -ITALIC_SKEW,
transformOrigin: bottomOrigin(m),
}));
const jsLetters = [...JS_IDS, ...THREED_IDS].map(id => meshes[id]);
// Baseline align the js row with where the letters settle, then keep them hidden until the sweep.
const jsBaselineShift = 1.9 - meshes['e'].position.y;
// Start the js behind the camera and tilted, the plug animation flies them onto the logo plane.
jsLetters.forEach(m => utils.set(m, {
y: m.position.y + jsBaselineShift,
z: 6.5,
rotateX: 20,
transformOrigin: bottomOrigin(m),
}));
utils.set(dot, {
x: dotRestX - .045,
y: 15,
scaleY: 0,
transformOrigin: bottomOrigin(dot),
});
utils.set(camera, {
x: 0,
y: 5.64,
z: 20.66,
rotateX: -14.73,
fov: 60,
zoom: 1,
near: 1,
far: 2000,
});
const DEG2RAD = Math.PI / 180;
// Hold the logo framing while the flatten narrows the fov, derive the dolly distance from fov each frame.
const FLATTEN_BASE_FOV = camera.fov;
const FLATTEN_DIST_NUM = camera.position.z * Math.tan(FLATTEN_BASE_FOV * 0.5 * DEG2RAD);
const flattenDezoom = { value: 1 };
// Render loop: hold the framing as the fov flattens, then draw the scene
createTimer({
priority: 0,
onUpdate: () => {
camera.position.z = FLATTEN_DIST_NUM * flattenDezoom.value / Math.tan(camera.fov * 0.5 * DEG2RAD);
renderer.render(scene, camera);
},
});
utils.set(skyMaterial, {
topColor: '#818198',
bottomColor: '#b6b6b9',
offset: -38,
exponent: 1.15,
});
utils.set(ambient, {
intensity: 0.6,
});
utils.set(keyLight, {
intensity: 1.6,
color: '#ffffff',
x: 0,
y: 30,
z: 0,
});
utils.set(rimLight, {
intensity: 0.6,
color: '#aab8ff',
x: -200,
y: -50,
z: 150,
});
utils.set(tiles, {
x: stagger(1, { grid: true, from: 'center', axis: 'x' }),
z: stagger(1, { grid: true, from: 'center', axis: 'z' }),
y: 0,
});
utils.set(floor, {
color: '#d4d4d8',
});
utils.set(cube, {
x: 0,
y: 0,
z: 0,
transformOriginY: -0.5,
});
utils.set(logoGroup, {
x: -10.922,
y: -0.31,
z: -0.5,
});
// Effect helpers: tile rings, seeded randoms, damped shake
const centerTile = tiles[Math.floor(FLOOR_ROWS / 2) * FLOOR_COLS + Math.floor(FLOOR_COLS / 2)];
// Center tiles within this radius explode at POP, tiles outside it stay as the remaining floor.
const EXPLODE_RADIUS = 10;
const tileRingDistance = (i) => Math.hypot(
(i % FLOOR_COLS) - Math.floor(FLOOR_COLS / 2),
Math.floor(i / FLOOR_COLS) - Math.floor(FLOOR_ROWS / 2),
);
const getCenterTiles = (radius) => tiles.filter((tile, i) => tileRingDistance(i) <= radius);
const getOuterTiles = (radius) => tiles.filter((tile, i) => tileRingDistance(i) > radius);
// Seeded random generators, one per source so each advances on its own and stays reproducible.
const explodeDurX = utils.createSeededRandom(4, 800, 1279);
const explodeDurZ = utils.createSeededRandom(0, 800, 1279);
const explodeDurY = utils.createSeededRandom(7, 800, 1279);
const explodeRotX = utils.createSeededRandom(7, -1080, 1080);
const explodeRotY = utils.createSeededRandom(13, -1080, 1080);
const explodeRotZ = utils.createSeededRandom(21, -1080, 1080);
const explodeScaleDur = utils.createSeededRandom(0, 500, 1200);
const burstRotX = utils.createSeededRandom(10, -540, 540);
const burstRotY = utils.createSeededRandom(7, -540, 540);
const burstRotZ = utils.createSeededRandom(12, -540, 540);
// Damped shake: drive a 0 -> 1 progress and map it to a decaying oscillation, replacing long
// hand-authored skew keyframe arrays. amplitude relaxes from amp toward settle, frequency sweeps up.
const shake = (amp, cycles, { decay = 1, sweep = 0, settle = 0, dir = 1, center = 0 } = {}) =>
t => center + dir * (settle + (amp - settle) * (1 - t) ** decay) * Math.sin(cycles * (t + sweep * t * t) * 2 * Math.PI);
// Run a shake only over the [start, 1] portion of the progress, for a delayed second hit.
const lateShake = (amp, cycles, start, opts = {}) => {
const inner = shake(amp, cycles, opts);
return t => t < start ? 0 : inner((t - start) / (1 - start));
};
// Master timeline
const tl = createTimeline({
id: 'Anime.js 3D logo',
autoplay: false,
})
.add(renderer.domElement, {
id: 'fade in',
// Fade the canvas up from the page background so the intro starts out of flat dark.
opacity: [0, 1],
duration: 1300, ease: 'inOut(2)',
}, 0)
.add(camera, {
id: 'camera follow',
rotateX: [
{ to: 38, duration: 700, ease: 'inOut(2)' },
{ to: 41, duration: 500, delay: 0, ease: 'inOutSine' },
{ to: 40.5, duration: 200, delay: 0, ease: 'inOutSine' },
{ to: 36, duration: 180, delay: 0, ease: 'in(2)' },
{ to: 24, duration: 80, delay: 0, ease: 'in(2)' },
{ to: -10, duration: 40, delay: 0, ease: 'in(1.5)' },
{ to: -16.5, duration: 90, delay: 20, ease: 'out(2)' },
{ to: -14.73, duration: 190, delay: 0, ease: 'inOutSine' },
],
y: [
{ to: 6.3, duration: 900, ease: 'inOut(2)' },
{ to: 6.4, duration: 500, delay: 0, ease: 'inOutSine' },
{ to: 6.15, duration: 180, delay: 0, ease: 'in(2)' },
{ to: 5.04, duration: 120, delay: 0, ease: 'in(2)' },
],
}, 300)
.add(cube, {
id: 'cube enter',
y: [
{ from: 24, to: 1, duration: 300, delay: 1700, ease: 'in(5.0825)' },
{ from: 0, to: 0.138, duration: 60, delay: 0, ease: cubicBezier(0, 1.1575, 0.5712, 0.9605) },
{ to: -0.75, duration: 1040, ease: cubicBezier(0, 1.1575, 0.5712, 0.9605) },
{ from: -1.75, to: -2.5, duration: 146, delay: 0, ease: 'out(9.8523)' },
],
scaleX: [
{ from: 0, to: 1, duration: 2000, delay: 0 },
{ from: 1.3, to: 1, duration: 100, ease: 'out(2)' },
],
scaleY: [
{ from: 5, to: 2, duration: 2000, delay: 0 },
{ to: 1.25, duration: 200, delay: 0, ease: cubicBezier(0.1, 0.7, 0.5763, 0.7728) },
{ to: 1, duration: 900, delay: 0, ease: cubicBezier(0.1, 0.7, 0.5763, 0.7728) },
],
scaleZ: [
{ from: 0, to: 1, duration: 2000 },
{ from: 1.3, to: 1, duration: 100, ease: 'out(2)' },
],
ease: 'in(2.4146)',
skewX: { from: 0, to: 1, duration: 1143, delay: 2000, ease: 'linear', modifier: shake(8, 18, { decay: 2 }) },
skewZ: { from: 0, to: 1, duration: 1143, delay: 2000, ease: 'linear', modifier: shake(8, 18, { decay: 2 }) },
}, 0)
.set(centerTile, { scale: 0 }, 2000)
.add(tiles, {
id: 'floor crash',
y: [
{ from: 0, to: stagger([0.15, 0], { from: 'center', ease: linear('0', '0.185 70.69%', '0.4644 84.63%', '0.7852 95.62%', '1'), grid: true }), duration: 50, delay: stagger([0, 451], { from: 'center', ease: 'in(2.5507)', grid: true }), ease: cubicBezier(0.5621, 0.9568, 0.5, 1) },
{ to: stagger([-1.5, 0], { from: 'center', ease: linear('0', '0.2812 12.72%', '0.5478 20.84%', '1 41.29%', '1'), grid: true, jitter: [0.102, 0], seed: 0 }), duration: 1050, delay: 0, ease: cubicBezier(0, 0.8, 0, 1.0128) },
{ to: stagger([-3, 0], { from: 'center', ease: linear('0', '0 14.17%', '0.5317 25.26%', '0.8904 47.35%', '1 47.8%', '1'), grid: true, jitter: [0.3, 0], seed: 0 }), duration: stagger([0, 656], { from: 'center', ease: linear('0', '0.0368 14.54%', '0.3042 62.23%', '1 62.24%', '1'), grid: true, jitter: [50, 0], seed: 0 }), delay: 0, ease: cubicBezier(0, 1.0807, 0.3512, 1.2537) },
],
rotateX: { to: stagger([0, 0], { from: 'center', ease: linear('0', '0 13.93%', '0.2419 40.77%', '1 55.03%', '1'), grid: true, jitter: [20, 0], seed: 6 }), duration: 1200, delay: 0 },
rotateY: { to: stagger([0, 0], { from: 'center', ease: linear('0', '0 13.93%', '0.2419 40.77%', '1 55.03%', '1'), grid: true, jitter: [20, 0], seed: 12 }), duration: 1260, delay: 0 },
rotateZ: { to: stagger([0, 0], { from: 'center', ease: linear('0', '0 13.93%', '0.2419 40.77%', '1 55.03%', '1'), grid: true, jitter: [20, 0], seed: 3 }), duration: 1200, delay: 50 },
}, 2000)
.add(camera, {
id: 'camera shake',
y: { from: 0, to: 1, duration: 1412, ease: 'linear', modifier: t => shake(0.12, 15, { decay: 2, center: 5.64 })(t) + lateShake(0.97, 2, 0.78, { decay: 1.6 })(t) },
rotateZ: { from: 0, to: 1, duration: 1437, ease: 'linear', modifier: t => shake(0.42, 10, { decay: 1.8 })(t) + lateShake(0.42, 2, 0.79, { decay: 1.4 })(t) },
ease: 'inOutSine',
}, 2000)
.add(cube, {
id: 'cube recover',
skewX: [
{ to: -9, duration: 58, ease: cubicBezier(0.3815, 0.0684, 0.5, 1) },
{ to: 7, duration: 63, delay: 0, ease: cubicBezier(0.3815, 0.0684, 0.5, 1) },
{ to: -5, duration: 71, delay: 0, ease: cubicBezier(0.3815, 0.0684, 0.5, 1) },
{ to: 3.5, duration: 77, delay: 0, ease: cubicBezier(0.3815, 0.0684, 0.5, 1) },
{ to: -2, duration: 82, delay: 0, ease: cubicBezier(0.3815, 0.0684, 0.5, 1) },
{ to: 1, duration: 90, delay: 0, ease: cubicBezier(0.3815, 0.0684, 0.5, 1) },
{ to: -0.5, duration: 96, delay: 0, ease: cubicBezier(0.3815, 0.0684, 0.5, 1) },
{ to: 0, duration: 83, delay: 0, ease: cubicBezier(0.3815, 0.0684, 0.5, 1) },
],
scaleY: [
{ to: 0.8, duration: 77, ease: 'out(3)' },
{ to: 1.12, duration: 102, delay: 0, ease: 'inOutSine' },
{ to: 0.94, duration: 109, delay: 0, ease: 'inOutSine' },
{ to: 1.05, duration: 115, delay: 0, ease: 'inOutSine' },
{ to: 0.98, duration: 109, delay: 0, ease: 'inOutSine' },
{ to: 0.97, duration: 108, delay: 0, ease: 'inOutSine' },
],
scaleX: [
{ to: 1.15, duration: 77, ease: 'out(3)' },
{ to: 0.93, duration: 102, delay: 0, ease: 'inOutSine' },
{ to: 1.04, duration: 109, delay: 0, ease: 'inOutSine' },
{ to: 0.97, duration: 115, delay: 0, ease: 'inOutSine' },
{ to: 1.01, duration: 109, delay: 0, ease: 'inOutSine' },
{ to: 1.02, duration: 108, delay: 0, ease: 'inOutSine' },
],
scaleZ: [
{ to: 1.15, duration: 77, ease: 'out(3)' },
{ to: 0.93, duration: 102, delay: 0, ease: 'inOutSine' },
{ to: 1.04, duration: 109, delay: 0, ease: 'inOutSine' },
{ to: 0.97, duration: 115, delay: 0, ease: 'inOutSine' },
{ to: 1.01, duration: 109, delay: 0, ease: 'inOutSine' },
{ to: 1.02, duration: 108, delay: 0, ease: 'inOutSine' },
],
y: [
{ to: -2.56, duration: 77, ease: 'out(3)' },
{ to: -2.47, duration: 128, delay: 0, ease: 'inOutSine' },
{ to: -2.52, duration: 140, delay: 0, ease: 'inOutSine' },
{ to: -2.53, duration: 275, delay: 0, ease: 'inOutSine' },
],
}, 3230)
.add(cube, {
id: 'cube expand',
scaleY: { to: 4, duration: 4150, delay: 800, ease: cubicBezier(0.2423, -0.0053, 0.3107, 0.5533) },
scaleX: { to: 0.8, duration: 4150, delay: 800, ease: cubicBezier(0.2423, -0.0053, 0.3107, 0.5533) },
scaleZ: { to: 0.8, duration: 4150, delay: 800, ease: cubicBezier(0.2423, -0.0053, 0.3107, 0.5533) },
y: { to: 0.75, duration: 4150, delay: 800, ease: cubicBezier(0.2423, -0.0053, 0.3107, 0.5533) },
skewX: { from: 0, to: 1, duration: 4150, delay: 800, ease: 'linear', modifier: shake(9.5, 21, { decay: 1.5, sweep: 1.2, settle: 3.1 }) },
skewZ: { from: 0, to: 1, duration: 4150, delay: 800, ease: 'linear', modifier: shake(4.3, 18, { decay: 0.7, sweep: 1.4, dir: -1 }) },
}, 3050)
.add(getCenterTiles(8), {
id: 'floor expand',
y: [
{ to: stagger([-3.5, -0.82], { from: 'center', ease: linear('0', '0.0878 0.01%', '0.3904 50.14%', '0.7766 68.95%', '1'), grid: true, jitter: [0.3, 0], seed: 0 }), duration: 260, delay: 0, ease: 'out(3)' },
{ to: stagger([1.4, -0.72], { from: 'center', ease: linear('0', '0 28.61%', '0.27 28.62%', '0.7766 68.95%', '1'), grid: true, jitter: [0.3, 0], seed: 0 }), duration: 4100, delay: 440, ease: cubicBezier(0.2423, -0.0053, 0.367, 0.5896) },
],
}, 3200)
.label('POP', 8000)
.label('slowmo start', 'POP-=0')
.label('slowmo end', 'POP+=500')
.add(camera, {
id: 'camera zoom in',
zoom: { to: 2, duration: 5500, delay: 0, ease: cubicBezier(0.6985, 0.1061, 0.5527, 0.7364) },
}, 2500)
.add(cameraRig, {
id: 'camera orbit',
rotateY: [
{ from: -270, to: -180, duration: 3600, delay: 0 },
{ to: 0, duration: 700, delay: 1000, ease: cubicBezier(0.375, -0.0148, 0, 1.0101) },
],
rotateX: { to: 0, duration: 1900, delay: 2700 },
y: [
{ to: 0, duration: 1900, delay: 2700 },
{ to: 0, duration: 300, delay: 0, ease: 'out(2)' },
],
ease: 'inOut(2)',
}, 3400)
.add(getCenterTiles(EXPLODE_RADIUS), {
id: 'floor explode',
x: { to: stagger([1, 40], { from: 'center', grid: true, axis: 'x', jitter: 5, seed: 0 }), duration: () => explodeDurX(), delay: stagger([0, 137], { from: 'center', grid: true }), ease: 'out(5.3605)' },
z: { to: stagger([1, 40], { from: 'center', grid: true, axis: 'z' }), duration: () => explodeDurZ(), delay: stagger([0, 137], { from: 'center', grid: true, jitter: 5, seed: 0 }), ease: 'out(5.3605)' },
y: { to: stagger([40, 1], { from: 'center', ease: cubicBezier(0.3326, 0.0289, 0.9886, 0.4057), grid: true, jitter: 5, seed: 0 }), duration: () => explodeDurY(), delay: stagger([0, 137], { from: 'center', grid: true }), ease: 'out(5.3605)' },
rotateX: { to: () => explodeRotX(), duration: 815, delay: stagger([0, 314], { from: 'center', grid: true }), ease: 'out(2)' },
rotateY: { to: () => explodeRotY(), duration: 815, delay: stagger([0, 314], { from: 'center', grid: true }), ease: 'out(2)' },
rotateZ: { to: () => explodeRotZ(), duration: 815, delay: stagger([0, 314], { from: 'center', grid: true }), ease: 'out(2)' },
scale: { to: 0, duration: () => explodeScaleDur(), delay: stagger([0, 314], { from: 'center', grid: true }), ease: 'out(2.8731)' },
duration: 113,
delay: stagger([0, 210], { from: 'center', grid: true, seed: 0 }),
ease: 'linear',
}, 'POP-=15')
.add([...getOuterTiles(EXPLODE_RADIUS), ...skirts], {
id: 'floor drop',
// Sink the remaining floor tiles and skirts straight down out of view, the exploded center is left alone.
y: { to: -50, duration: 604, delay: 146, ease: cubicBezier(0.8109, 0.0308, 0.9152, 0.5479) },
}, 'POP-=150')
.add(letters, {
id: 'letters pop',
// 2D POP letters bounce. translateY -> local y (2D value x -0.01118), scales and eases ported 1:1.
y: [
{ from: -0.39, to: 3.23, duration: 240, ease: cubicBezier(0.225, 1, 0.915, 0.98) },
{ to: 1.9, duration: 120, delay: 20, ease: 'inQuad' },
{ to: 1.9, duration: 120, delay: 0, ease: 'outQuad' },
],
scaleX: [
{ to: [0.25, 0.85], duration: 240, ease: 'outQuad' },
{ to: 1.08, duration: 120, delay: 85, ease: 'inOutSine' },
{ to: 1, duration: 260, delay: 25, ease: 'outQuad' },
],
scaleY: [
{ to: [0.4, 1.5], duration: 120, ease: 'outSine' },
{ to: 0.6, duration: 120, delay: 180, ease: 'inOutSine' },
{ to: 1.2, duration: 180, delay: 25, ease: 'outQuad' },
{ to: 1, duration: 190, delay: 15, ease: 'outQuad' },
],
duration: 400,
ease: 'outSine',
delay: stagger(60, { from: 'center' }),
}, 'POP')
.add(cube, {
id: 'cube dot pop',
// 2D #dot-1 rotate bounce, ported. Launches from the floor into the dot rest above the i. translateY -> world y (x -0.0435).
y: [
{ from: 0.75, to: 16.9, duration: 240, delay: 0, ease: cubicBezier(0.225, 1, 0.915, 0.98) },
{ to: 7.58, duration: 180, delay: 120, ease: 'inQuad' },
{ to: 11.7, duration: 250, delay: 0, ease: cubicBezier(0.225, 1, 0.915, 0.98) },
{ to: 9.11, duration: 170, delay: 20, ease: 'inQuad' },
{ to: 9.5, duration: 120, delay: 0, ease: 'outQuad' },
],
scaleX: { to: [0.8, 1], duration: 260, delay: 0, ease: 'outQuad' },
scaleY: { from: 4, to: 1, duration: 120, delay: 0, ease: 'outQuad' },
scaleZ: { to: [0.8, 1], duration: 260, delay: 0, ease: 'outQuad' },
transformOriginY: { to: 0, duration: 150, delay: 0, ease: 'out(2)' },
rotateZ: [
{ to: -540, duration: 480, delay: 20, ease: cubicBezier(0.7865, 0.9809, 0.8395, 0.9559) },
{ to: -720, duration: 540, delay: 160, ease: 'outSine' },
],
skewX: { to: 0, duration: 180, delay: 0, ease: 'out(2)' },
skewZ: { to: 0, duration: 180, delay: 0, ease: 'out(2)' },
ease: 'outSine',
}, 'POP')
.add(wrapper, {
id: 'logo punch',
// 2D #logo punch.
scale: { from: 1.25, to: 1, duration: 600, delay: 0 },
y: { from: -5.28, to: 0, duration: 600, delay: 0 },
duration: 900,
ease: 'outExpo',
}, 'POP')
.add(meshes['i-bar'], {
id: 'i squish',
// 2D #i-1 squish.
scaleY: [
{ to: 0.25, duration: 150, delay: 0, ease: 'outExpo' },
{ to: 1, duration: 700, delay: 0, ease: 'outElastic(2.11, 0.61)' },
],
scaleX: [
{ to: 1.5, duration: 50, delay: 0, ease: 'outSine' },
{ to: 1, duration: 900, delay: 0, ease: 'outElastic(2.11, 0.61)' },
],
}, 'POP+=380')
.add(camera, {
id: 'camera pop',
// End looking straight at the letters, level (rotateX 0).
y: { to: 7, duration: 1100, ease: 'out(4.9225)' },
rotateX: [
{ to: 9.1, duration: 260, ease: 'out(3.2713)' },
{ to: 0, duration: 700, ease: 'out(3)', delay: 140 },
],
zoom: { to: 1, duration: 400, ease: 'out(3)' },
}, 'POP')
.add(letters, {
id: 'letters skew',
// 2D SWEECH, remove the counter-skew so the letters slant into italic.
skewX: 0,
duration: 1350,
ease: 'outElastic(1.1, .9)',
}, 'POP+=1050')
.add(cube, {
id: 'cube sweep',
// 2D dot SWEECH swoosh, cube sweeps sideways toward the e and leans into the italic.
x: { to: 16.4, duration: 1350 },
skewX: { to: 8, duration: 1350 },
duration: 1450,
ease: 'outElastic(1.1,1.7)',
}, 'POP+=1050')
.add(jsLetters, {
id: 'js plug',
// Fly in from behind the camera and level the tilt as they plug onto the logo.
z: { from: -2, to: 0.12825, duration: 1295 },
rotateX: { from: -120, to: 0, duration: 1295 },
delay: stagger(50),
ease: 'outElastic(1.1, .9)',
scale: { from: 0, to: 1, duration: 549 },
}, 'POP+=1055')
.add(logoGroup, {
id: 'logo recenter',
// Slide the anime js lockup left to re-center now that js widened it; the cube sweep lands pre-shifted to match.
x: { to: '-=8.7', duration: 1350, ease: 'outElastic(1.1, .9)' },
duration: 1250,
ease: 'inOutQuint',
}, 'POP+=1050')
.add(camera, {
id: 'camera flatten',
// Narrow the fov to flatten the perspective, the render loop derives z from fov so the framing holds.
fov: { to: 2, duration: 700, ease: 'out(2)' },
}, 'POP+=1450')
.add(flattenDezoom, {
id: 'camera dezoom',
// Pull the framing back a touch as it flattens, the render loop scales the dolly distance by this.
value: { to: 1.5, duration: 1125, delay: 0 },
duration: 1150, ease: 'out(2)',
}, 'POP+=1050')
.add(skyMaterial, {
id: 'sky to bg',
// Fade the gradient dome to the flat background color for a clean dark end card.
topColor: BG, bottomColor: BG,
duration: 465, ease: 'inOut(4.348)',
}, 'POP+=1500')
.add(ambient, {
id: 'ambient up',
// Lift ambient as the background goes flat dark so the logo stays bright.
intensity: [{ to: 3, duration: 300 }, { to: 1, duration: 700, delay: 1050 }],
duration: 240, ease: 'inOut(1.8042)',
}, 'POP+=1650')
.add(camera, {
id: 'outro depth',
// Re-add perspective so the meshes flying at the camera read with depth.
fov: 35,
duration: 1500, ease: 'out(2)',
}, 11000)
.add([...letters, ...jsLetters, cube], {
id: 'outro burst',
// Fly forward and up at the lens, staggered from the end.
z: 62.1,
y: 0.5,
rotateX: () => burstRotX(),
rotateY: () => burstRotY(),
rotateZ: () => burstRotZ(),
delay: stagger(100, { from: 'last', ease: 'in(2.3145)' }),
duration: 1140, ease: 'in(2)',
}, 10960)
.init()
// Playback: scrub the timeline forward with a slow-mo dip
const player = animate(tl, {
id: 'Player',
currentTime: [
{ to: () => tl.labels['slowmo start'], duration: () => tl.labels['slowmo start'], ease: cubicBezier(1, 0.7, 1, 0.85) },
{ to: () => tl.labels['slowmo end'], duration: 2000, ease: cubicBezier(0.0314, 0.3616, 0.8994, -0.2122) },
{ to: () => tl.duration, duration: () => tl.duration - tl.labels['slowmo end'] }
],
duration: tl.duration,
loop: true,
ease: 'linear',
});
In this project, we created a stunning 3D logo animation using HTML, CSS, JavaScript, Anime.js, and Three.js. We learned how to convert an SVG into a 3D model, add smooth animations, camera movements, lighting, and visual effects to create a modern and interactive web experience. This project is a great way to improve your animation skills and understand the basics of 3D graphics on the web.
If your project has problems, don’t worry. Just click to download the source code and face your coding challenges with excitement. Have fun coding!
