Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Intelligence View
⚡ tsecurity.de Intelligence

Build an award Winning 3D Website with scroll-based animations | Next.js, three.js & GSAP

If you wish to go through this tutorial on YouTube please visit this link 👆👆👆 Building a 3D Earth Hero Section in Next.js, Three.js, and GLSL If you want your portfolio or product site to be remembered, a flat hero section i…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

If you wish to go through this tutorial on YouTube please visit this link





👆👆👆







Building a 3D Earth Hero Section in Next.js, Three.js, and GLSL



If you want your portfolio or product site to be remembered, a flat hero section is not always enough. A small 3D moment that feels alive can quickly turn a generic page into something people talk about.



In the video, we build a fully interactive 3D Earth hero section with:




  • Next.js (App Router)

  • React and TypeScript

  • Three.js

  • Custom GLSL shaders for day, night, clouds, and atmosphere

  • Lenis and GSAP ScrollTrigger for smooth scroll based animation



This post walks through the main steps from the video, using the real code from the project.



Repo: https://github.com/Robinzon100/3D_hero







1. Turn the hero into a client side canvas



We start by making the home page a client component and giving it a dedicated canvas for the planet.




"use client";
import { useEffect } from "react";

import initPlanet3D from "@/components/3D/planet";

export default function Home() {
useEffect(() => {
initPlanet3D();
}, []);

return (
<div className="page">
<section className="hero_main">
<canvas className="planet-3D" />
</section>
</div>
);
}






The idea is simple:




  • React renders the layout and the canvas.


  • initPlanet3D takes over the canvas and mounts Three.js inside it.

  • Everything 3D lives in components/3D/planet.ts.









2. Bootstrapping the Three.js scene



Inside planet.ts we first grab the canvas and create a scene:




import * as THREE from "three";

const initPlanet = (): { scene: THREE.Scene } => {
const canvas = document.querySelector(
"canvas.planet-3D"
) as HTMLCanvasElement;
const scene = new THREE.Scene();

return { scene };
};






Next, we add the core building blocks: sizes, camera, and renderer.




// Sizes
const sizes = {
width: window.innerWidth,
height: window.innerHeight,
pixelRatio: window.devicePixelRatio,
};

// Camera
const camera = new THREE.PerspectiveCamera(
15,
sizes.width / sizes.height,
0.1,
10000
);
camera.position.x = 0;
camera.position.y = 0.1;
camera.position.z = 19;
scene.add(camera);

// Renderer
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(sizes.pixelRatio);
renderer.setClearColor(0x000000, 0);
renderer.outputColorSpace = THREE.SRGBColorSpace;






Key points:




  • Low FOV (15) makes the planet feel large and cinematic.

  • The camera is slightly above on the Y axis and pulled back on Z, so the planet sits nicely in frame.

  • Transparent clear color lets us overlay regular HTML UI behind the planet.









3. Loading high quality Earth textures



To make the sphere look like Earth, we load three textures:




  • Day color map

  • Night lights map

  • Specular cloud map (for both clouds and reflections)




const TL = new THREE.TextureLoader();
const dayTexture = TL.load("./earth/day.jpg");
const nightTexture = TL.load("./earth/night.jpg");
const specularCloudsTexture = TL.load("./earth/specularClouds.jpg");

dayTexture.colorSpace = THREE.SRGBColorSpace;
nightTexture.colorSpace = THREE.SRGBColorSpace;

const baseAnisotropy = renderer.capabilities.getMaxAnisotropy();

dayTexture.anisotropy = baseAnisotropy;
specularCloudsTexture.anisotropy = baseAnisotropy;
nightTexture.anisotropy = baseAnisotropy;






anisotropy makes textures stay sharp at grazing angles. Without it, the planet would look muddy along the edges.









4. First pass: a basic shader material



Before we go full Earth shader, we create a basic shader to make sure all plumbing works.




const earthGeometry = new THREE.SphereGeometry(2, 64, 64);
const earthMaterial = new THREE.ShaderMaterial({
vertexShader: `
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;

void main() {
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * viewMatrix * modelPosition;

vec3 modelNormal = (modelMatrix * vec4(normal, 0.0)).xyz;

vUv = uv;
vNormal = modelNormal;
vPosition = modelPosition.xyz;
}
`
,
fragmentShader: `
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;

void main() {
vec3 viewDirection = normalize(vPosition - cameraPosition);
vec3 normal = normalize(vNormal);
gl_FragColor = vec4(normal, 1.0);
}
`
,
transparent: true,
});

const earth = new THREE.Mesh(earthGeometry, earthMaterial);
scene.add(earth);






The fragment shader just uses the normal as color. It is not pretty, but it proves:




  • The geometry is correct.

  • The shader compiles.

  • Our camera and renderer are set up correctly.









5. Smooth scrolling and the render loop



Next we wire Lenis and GSAP so that:




  • Scroll feels smooth.

  • The render loop and scroll are in sync.




const lenis = new Lenis({
duration: 1.2,
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
});

lenis.on("scroll", ScrollTrigger.update);

gsap.ticker.add((time) => {
lenis.raf(time * 1000);
renderer.render(scene, camera);
});

gsap.ticker.lagSmoothing(0);






GSAP drives everything using its ticker, Lenis updates scroll, and we render on each tick.



Later, we add rotation and resize handling inside the same loop:




gsap.ticker.add((time) => {
lenis.raf(time * 1000);
earth.rotation.y = time * 0.2;
renderer.render(scene, camera);
});

window.addEventListener("resize", () => {
sizes.width = window.innerWidth;
sizes.height = window.innerHeight;
sizes.pixelRatio = Math.min(window.devicePixelRatio, 2);

camera.aspect = sizes.width / sizes.height;
camera.updateProjectionMatrix();

renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(sizes.pixelRatio);
});






Now the planet slowly spins and the scene stays pixel perfect on resize.









6. Splitting shaders into GLSL files



Writing GLSL inline inside TypeScript gets messy fast. So we install raw-loader and tell Turbopack to load .glsl files as raw strings.




npm i raw-loader -D






next.config.ts:




import type { NextConfig } from "next";

const nextConfig: NextConfig = {
turbopack: {
rules: {
"*.{glsl,vs,fs,vert,frag}": {
loaders: ["raw-loader"],
as: "*.js",
},
},
},
reactCompiler: true,
};

export default nextConfig;






Then we move our shaders to shaders/earth/vertex.glsl and shaders/earth/fragment.glsl:




// earth.vertex.glsl
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;

void main() {
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * viewMatrix * modelPosition;

vec3 modelNormal = (modelMatrix * vec4(normal, 0.0)).xyz;

vUv = uv;
vNormal = modelNormal;
vPosition = modelPosition.xyz;
}









// earth.fragment.glsl
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;

void main() {
vec3 viewDirection = normalize(vPosition - cameraPosition);
vec3 normal = normalize(vNormal);
gl_FragColor = vec4(normal, 1.0);
}






And reference them in planet.ts:




const earthMaterial = new THREE.ShaderMaterial({
vertexShader: earthVertexShader,
fragmentShader: earthFragmentShader,
uniforms: {
uDayTexture: new THREE.Uniform(dayTexture),
uNightTexture: new THREE.Uniform(nightTexture),
uSpecularCloudsTexture: new THREE.Uniform(specularCloudsTexture),
uSunDirection: new THREE.Uniform(new THREE.Vector3(-1, 0, 0)),
uAtmosphereDayColor: new THREE.Uniform(
new THREE.Color(earthParameters.atmosphereDayColor)
),
uAtmosphereTwilightColor: new THREE.Uniform(
new THREE.Color(earthParameters.atmosphereTwilightColor)
),
},
transparent: true,
});

const earth = new THREE.Mesh(earthGeometry, earthMaterial);
scene.add(earth);












7. The real Earth shader



Now that the structure is clean, we build the real fragment shader: day, night, clouds, atmosphere, reflection, and specular highlights.




uniform sampler2D uDayTexture;
uniform sampler2D uNightTexture;
uniform sampler2D uSpecularCloudsTexture;
uniform vec3 uSunDirection;
uniform vec3 uAtmosphereDayColor;
uniform vec3 uAtmosphereTwilightColor;

varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;

void main() {
vec3 viewDirection = normalize(vPosition - cameraPosition);
vec3 normal = normalize(vNormal);
vec3 color = vec3(0.0);

vec3 dayColor = texture(uDayTexture, vUv).rgb;
vec3 nightColor = texture(uNightTexture, vUv).rgb;
vec2 specularCloudsColor = texture(uSpecularCloudsTexture, vUv).rg;

// Sun orientation
float sunOrientation = dot(uSunDirection, normal);

// Day / night color
float dayMix = smoothstep(-0.25, 0.5, sunOrientation);
color += mix(nightColor, dayColor, dayMix) * 2.0;

// Clouds
float cloudsMix = smoothstep(0.5, 1.0, specularCloudsColor.g * 1.1);
cloudsMix *= dayMix;
color = mix(color, vec3(1.0), cloudsMix);

// Fresnel
float fresnel = dot(viewDirection, normal) + 1.1;
fresnel = pow(fresnel, 2.0);

// Atmosphere
float atmosphereDayMix = smoothstep(-0.5, 1.0, sunOrientation);
vec3 atmosphereColor = mix(
uAtmosphereTwilightColor,
uAtmosphereDayColor,
atmosphereDayMix
);
color = mix(color, atmosphereColor, fresnel * atmosphereDayMix);

// Specular
vec3 reflection = reflect(-uSunDirection, normal);
float specular = -dot(reflection, viewDirection);
specular = max(specular, 0.0);
specular = pow(specular, 10.0);
specular *= specularCloudsColor.r * 0.7;

vec3 specularColor = mix(vec3(1.0), atmosphereColor, fresnel);
color += specular * specularColor;

// Final color
gl_FragColor = vec4(color, 1.0);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}






Highlights:





  • sunOrientation via dot(uSunDirection, normal) tells us which side is lit.


  • dayMix uses smoothstep to blend day and night softly.

  • Clouds are pulled from the green channel of the specular texture and only appear on the day side.


  • fresnel gives that soft blue rim glow near the edges.

  • Reflections and specular use the red channel of the specular texture to limit highlights to oceans.



We also add a spherical helper in planet.ts to control the sun direction:




// Coordinates
let sunSpherical = new THREE.Spherical(1, Math.PI * 0.48, -1.8);
const sunDirection = new THREE.Vector3();

// Sun direction
sunDirection.setFromSpherical(sunSpherical);

// Uniforms
earthMaterial.uniforms.uSunDirection.value.copy(sunDirection);
atmosphereMaterial.uniforms.uSunDirection.value.copy(sunDirection);












8. Atmosphere mesh and shaders



To make the glow feel believable, the atmosphere is a separate mesh slightly larger than Earth and rendered on the back side.




const atmosphereMaterial = new THREE.ShaderMaterial({
side: THREE.BackSide,
transparent: true,
vertexShader: atmosphereVertexShader,
fragmentShader: atmosphereFragmentShader,
uniforms: {
uOpacity: { value: 1 },
uSunDirection: new THREE.Uniform(new THREE.Vector3(0, 0, 1)),
uAtmosphereDayColor: new THREE.Uniform(
new THREE.Color(atmosphereDayColor)
),
uAtmosphereTwilightColor: new THREE.Uniform(
new THREE.Color(atmosphereTwilightColor)
),
},
depthWrite: false,
});

const atmosphere = new THREE.Mesh(earthGeometry, atmosphereMaterial);
atmosphere.scale.set(1.13, 1.13, 1.13);

const earthGroup = new THREE.Group().add(earth, atmosphere);
scene.add(earthGroup);






Atmosphere fragment shader:




uniform vec3 uSunDirection;
uniform vec3 uAtmosphereDayColor;
uniform vec3 uAtmosphereTwilightColor;
uniform float uOpacity;

varying vec3 vNormal;
varying vec3 vPosition;

void main() {
vec3 viewDirection = normalize(vPosition - cameraPosition);
vec3 normal = normalize(vNormal);
vec3 color = vec3(0.0);

// Sun orientation
float sunOrientation = dot(uSunDirection, normal);

float atmosphereDayMix = smoothstep(-0.5, 1.0, sunOrientation);
vec3 atmosphereColor = mix(
uAtmosphereTwilightColor,
uAtmosphereDayColor,
atmosphereDayMix
);
color = mix(color, atmosphereColor, atmosphereDayMix);
color += atmosphereColor;

float edgeAlpha = dot(viewDirection, normal);
edgeAlpha = smoothstep(0.0, 1.3, edgeAlpha);

float dayAlpha = smoothstep(-0.5, 0.0, sunOrientation);

float alpha = edgeAlpha * dayAlpha;

// Final color
gl_FragColor = vec4(color, alpha);
gl_FragColor.a *= uOpacity;
#include <tonemapping_fragment>
#include <colorspace_fragment>
}






This gives us:




  • A soft ring of atmosphere that fades at the edges.

  • Atmosphere color that respects the sun direction.

  • Alpha that fades toward the dark side of the planet.



The rotation now happens on earthGroup so Earth and atmosphere stay aligned:




gsap.ticker.add((time) => {
lenis.raf(time * 1000);
earthGroup.rotation.y = time * 0.2;
renderer.render(scene, camera);
});












9. Scroll based hero animation and UI



The 3D is only half of the hero. We also need content and a scroll moment that feels intentional.



Updated page.tsx:




"use client";

import { useEffect } from "react";
import initPlanet3D from "@/components/3D/planet";

export default function Home() {
useEffect(() => {
initPlanet3D();
}, []);

return (
<div className="page">
<section className="hero_main">
<div className="content">
<h1>Welcome To The New World</h1>

<p>
AI agents that actually bring value to businesses and elevate
workers productivity.
</p>

<button className="cta_btn">Get started.</button>
</div>
<canvas className="planet-3D" />
</section>
</div>
);
}






Then we wire ScrollTrigger:




gsap.registerPlugin(ScrollTrigger);

gsap
.timeline({
scrollTrigger: {
trigger: ".hero_main",
start: () => "top top",
scrub: 3,
anticipatePin: 1,
pin: true,
},
})
.to(
".hero_main .content",
{
filter: "blur(40px)",
autoAlpha: 0,
scale: 0.5,
duration: 2,
ease: "power1.inOut",
},
"setting"
)
.to(
camera.position,
{
y: 0.1,
z: window.innerWidth > 768 ? 19 : 30,
x: window.innerWidth > 768 ? 0 : 0.1,
duration: 2,
ease: "power1.inOut",
},
"setting"
);






As the user scrolls:




  • The text blurs and fades out.

  • The camera pulls back and reframes the planet.

  • The hero pins so the transition feels more like a scene than a regular scroll.









10. Cleaning up WebGL properly



To avoid memory leaks in single page apps, we dispose the renderer when the component unmounts. That requires initPlanet to return the renderer as well:




import * as THREE from "three";

const initPlanet = (): {
scene: THREE.Scene;
renderer: THREE.WebGLRenderer;
} => {
const canvas = document.querySelector(
"canvas.planet-3D"
) as HTMLCanvasElement;
const scene = new THREE.Scene();

const sizes = {
width: window.innerWidth,
height: window.innerHeight,
pixelRatio: window.devicePixelRatio,
};

const camera = new THREE.PerspectiveCamera(
15,
sizes.width / sizes.height,
0.1,
10000
);
scene.add(camera);

const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(sizes.pixelRatio);

return { scene, renderer };
};






And in the React effect:




useEffect(() => {
const { scene, renderer } = initPlanet3D();

return () => {
if (renderer) {
const gl = renderer.getContext?.();
gl?.getExtension("WEBGL_lose_context")?.loseContext();
renderer.dispose();
}
};
}, []);






WEBGL_lose_context helps free GPU resources and keeps the dev server happy during hot reloads.









Wrap up



The final hero combines:




  • A custom Earth shader with day, night, clouds, atmosphere, reflections, and specular highlights.

  • A dedicated atmosphere mesh for the glow around the rim.

  • Smooth Lenis scrolling and GSAP ScrollTrigger for the camera and text animation.

  • Clean React integration and renderer cleanup in Next.js.



You can use this as a template for your own 3D heroes: swap the textures, adjust the lighting, or plug in a completely different GLSL effect. The core structure stays the same:



Canvas in React, Three.js scene, GLSL shaders for style, GSAP for movement.



If you want to follow along line by line, the full code is in the repo:



https://github.com/Robinzon100/3D_hero

1. Sofort-Triage & Abwehrmaßnahmen

SOC Incident Playbook: Remote Code Execution (RCE) Defense
Syntax validiert (0 Fehler)
title: Detect Exploitation - Build an award Winning 3D Website with scroll-based animations | Next.js, three.js & GSAP
id: 46d2eefe-929b-4f0d-a73c-edb19d762d20
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-25
logsource:
  category: network_connection
  product: any
detection:
  selection:
      DestinationHostname:
        - 'modelposition.xyz'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
Syntax validiert (0 Fehler)
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-25"
        description = "YARA Signature for "
    strings:
        $str = "Build an award Winning 3D Webs" ascii wide
    condition:
        any of them
}
Syntax validiert (0 Fehler)
index=security sourcetype IN ("cisco:asa", "pan:traffic", "zeek_conn", "suricata", "WinEventLog:Security")
(dest_host="modelposition.xyz")
| stats count earliest(_time) as first_seen latest(_time) as last_seen by src_ip, dest_ip, dest_host, signature
| eval first_seen=strftime(first_seen, "%Y-%m-%d %H:%M:%S"), last_seen=strftime(last_seen, "%Y-%m-%d %H:%M:%S")
| sort - count
Syntax validiert (0 Fehler)
destination.domain: ("modelposition.xyz") and event.category: "network"
Syntax validiert (0 Fehler)
CommonSecurityLog
| where DestinationHostName in ("modelposition.xyz")
| summarize EventCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by SourceIP, DestinationIP, DestinationPort, Activity
| extend DetectionRule = "iShareStuff-CTI-Compiled"
| sort by EventCount desc

2. Cyber Threat Intelligence & Forensik

IoC Intelligence (1 Indikatoren)
modelposition[.]xyz
CTI Threat Relationship Graph3 Knoten / 2 Relationen
CVE / Incident Software MITRE ATT&CK CWE Weakness IoC
🎯
MITRE ATT&CK Matrix Navigator 14 Taktiken
Reconnaissance
-
Resource Development
-
Initial Access
Execution
Persistence
-
Privilege Escalation
Defense Evasion
Credential Access
-
Discovery
-
Lateral Movement
-
Collection
-
Command and Control
Exfiltration
-
Impact
tsecurity.de Cognitive Threat RAG
Fokus-Vektor:

Kognitive Analyse für identifizierte Bedrohung: Erhöhte Bedrohungslage im Bereich Build an award Winning 3D Website with s.... Basierend auf 368k Vektor-Korrelationen werden sofortige Isolationsmaßnahmen für betroffene Endpunkte empfohlen.

🛡️ Angriffsfläche & Exposure

Netzwerk/Remote-Zugriff ohne Vorauthentifizierung möglich.

⚡ Empfohlene Sofortmaßnahmen
  • 1. Perimeter-Inspektion: Relevante Portfreigaben und exponierte Endpunkte unverzüglich scannen.
  • 2. Patch-Applikation: Hersteller-Hotfix einspielen oder betroffene Daemons in isolierte DMZ-Segmente überführen.
  • 3. Telemetrie & EDR-Alerts: Prozessaufrufe und Child-Processes auf anomale Shell-Spawns überwachen.
🔗 Semantisch verwandte Zero-Days MariaDB 11.7 VEC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Build an award Winning 3D Website with scroll-based animations | Next.js, three.js & GSAP

Thematisch verwandte Begriffe: Build, award, Winning, Website · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-93647 | An unauthenticated calendar sender can place active markup in a COUNTER …
Advisory →
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag