AWW.register()
ทุก effect คือ factory คืน {destroy, setActive} ลงทะเบียนด้วยชื่อเดียว
คลังเทคนิคระดับ Awwwards เวอร์ชันเจาะลึก — immersive WebGL, custom GLSL, scroll-driven cinema. ทุกชิ้นเล่นจริง พร้อม pause เองเมื่อออกนอกจอ
v1 คือพจนานุกรม v2 คือสตูดิโอ ทุก effect ลงทะเบียนใน registry เดียว, mount แบบ lazy ตอนเข้าใกล้จอ, หยุด render loop อัตโนมัติเมื่อเลื่อนผ่าน, และมี destroy hook — สถาปัตยกรรมเดียวกับเว็บที่ชนะ Awwwards จริง
ทุก effect คือ factory คืน {destroy, setActive} ลงทะเบียนด้วยชื่อเดียว
mount เมื่อเข้าใกล้ viewport 200px ไม่กินทรัพยากรล่วงหน้า
ออกนอกจอ → หยุด rAF/WebGL ทันที ประหยัด GPU + แบต
เคารพ prefers-reduced-motion ทุกชิ้น มี fallback นิ่ง
พื้นผิวลื่นไหลที่คำนวณต่อพิกเซลบน GPU ด้วย domain-warped fBm noise — ไม่มี texture ไม่มี mesh มีแค่สมการ. นี่คือหัวใจของ background แบบ Hatom/Active Theory.
fBm = ซ้อน noise หลายความถี่ (octaves) ให้ได้รายละเอียดแบบเมฆ. "Domain warp" คือเอา noise ไปบิดพิกัดก่อนสุ่มอีกชั้น (fbm(p + fbm(p))) เกิดลายม้วนเป็นของเหลว.
octaves 4–6: รายละเอียด vs ความเร็วuTime*0.1: ความเร็วการไหลc1/c2/c3: เปลี่ยนอารมณ์ทั้งฉากp*scale: ซูมลายเข้า/ออก// fragment shader — หัวใจอยู่ที่ domain warp float fbm(vec2 p){ float v=0.,a=.5; for(int i=0;i<6;i++){ v+=a*noise(p); p*=2.; a*=.5; } return v; } void main(){ vec2 p=vUv*3.; float t=uTime*.1; vec2 q=vec2(fbm(p+t), fbm(p+vec2(5.2,1.3)-t)); // warp float n=fbm(p+q*2.+uMouse*.5); vec3 col=mix(C_VIOLET, C_MAGENTA, smoothstep(.2,.7,n)); col=mix(col, C_AMBER, smoothstep(.6,1.,n)*.6); gl_FragColor=vec4(col*(.6+.6*n), 1.); }
เทคนิคตรงจากภาพ Hatom ที่ส่งมา — เมาส์ลากผ่านแล้วเนื้อหา "บิด" เป็นคลื่น พร้อมแยกช่องสี RGB เกิด chromatic aberration. ทำบน plane เดียวด้วย displacement + color-shift ใน fragment shader.
ระยะจากเมาส์ (distance(uv,uMouse)) คุม 2 อย่าง: ความแรงคลื่น (sin ตามระยะ+เวลา) และระยะแยกสี. smoothstep ทำให้ effect จางหายนุ่มที่ขอบรัศมี ไม่มีเส้นตัด.
ripple amp .02: ความแรงคลื่นfreq 30: ความถี่คลื่นshift .012: ระยะแยก RGBlerp .08 ของ uMouse/uHover: ความหนืดการตาม// fragment — displacement เป็นคลื่น + RGB split ตามระยะเมาส์ float d=distance(vUv,uMouse); float fall=smoothstep(.55,.0,d)*uHover; // 1 ใกล้เมาส์ → 0 ไกล vec2 dir=normalize(vUv-uMouse+1e-4); vec2 uv=vUv+dir*sin(d*30.-uTime*3.)*.02*fall; // ripple float s=.012*fall; float r=texture2D(uTex,uv+vec2(s,0.)).r; float g=texture2D(uTex,uv).g; float b=texture2D(uTex,uv-vec2(s,0.)).b; gl_FragColor=vec4(r,g,b,1.);
เคอร์เซอร์วงแหวนที่ตามมือแบบหน่วง (ดูทั่วทั้งหน้านี้) + ปุ่ม "แม่เหล็ก" ที่ดูดตัวเองเข้าหาเมาส์เมื่อเข้าใกล้ — ลายเซ็นของเว็บ studio ระดับโลก.
เคอร์เซอร์จริงถูกซ่อน แล้ว lerp ตำแหน่ง div ตามเมาส์ทุกเฟรม (x += (mouseX-x)*0.15) เกิดการตามแบบสปริง. Magnetic = วัดระยะเมาส์ถึงจุดกึ่งกลางปุ่ม แล้วเลื่อนปุ่มเข้าหา ~30% ของระยะ.
.15 วงแหวน / .35 จุด: ความหนืด90px: ระยะเริ่มมีแรง.3: แรงดูดpointer:coarse (มือถือ)// magnetic pull el.addEventListener('pointermove',(e)=>{ const r=el.getBoundingClientRect(); const mx=e.clientX-(r.left+r.width/2), my=e.clientY-(r.top+r.height/2); gsap.to(el,{x:mx*.3,y:my*.3,duration:.4,ease:'power3.out'}); }); el.addEventListener('pointerleave',()=> gsap.to(el,{x:0,y:0,duration:.7,ease:'elastic.out(1,.3)'}));
แถบข้อความวิ่งวนไม่รู้จบ — แต่เร่งและ เอียง (skew) ตามความเร็วการ scroll ของผู้ใช้ ทำให้หน้ารู้สึก "มีแรงเฉื่อย" เหมือนของจริงมีมวล.
track ถูก loop ด้วย modulo: เลื่อน x ลบเรื่อยๆ แล้ว wrap เมื่อพ้นความกว้างชุดแรก. ความเร็ว scroll (จาก Lenis velocity) บวกเข้า speed ฐาน และ map เป็น skewX — เลื่อนแรง = เอียงมาก.
base 0.6: ความเร็ววิ่งตลอดvFactor: scroll แปลงเป็นความเร็วเท่าไรskew*: ความไวการเอียง// ทุกเฟรม: เลื่อน + wrap + skew ตามความเร็ว scroll x -= (base + Math.abs(vel)*.4); if (x <= -setW) x += setW; // loop ไม่รู้จบ skew += (gsap.utils.clamp(-20,20,vel*.6) - skew)*.1; track.style.transform = `translateX(${x}px) skewX(${skew}deg)`;
ตัวอักษรสุ่มมั่วแล้วค่อยๆ "ถอดรหัส" เป็นคำจริงทีละตัว — ให้ความรู้สึกระบบกำลังประมวลผล. หัวข้อ intro ด้านบนก็ใช้ตัวนี้.
แต่ละตัวอักษรมี "เวลาเริ่ม" และ "เวลาจบ" ต่างกัน. ก่อนถึงเวลาจบ แสดงอักขระสุ่มจากชุด glyph; เมื่อ progress ผ่านจุดจบ ล็อกเป็นตัวจริง. ไล่จากซ้ายไปขวาด้วยการ stagger เวลาเริ่ม.
data-text เพื่อ screen readerduration: ความเร็วถอดรหัสรวม// แต่ละตัวล็อกเป็นตัวจริงเมื่อ progress > เวลาจบของมัน const out = real.split('').map((ch,i)=>{ if (ch===' ') return ' '; const end=(i+1)/len; return p >= end ? ch : glyphs[(Math.random()*glyphs.length)|0]; }).join(''); elTextNode = out;
สองแผ่นเปิดสวนทางกันตามการ scroll แบบ scrub (ผูก 1:1 กับ scrollbar) — เทคนิค panel/curtain ที่เว็บ portfolio ใช้คั่น section.
ScrollTrigger scrub:true map ตำแหน่ง scroll → progress 0→1 แล้ว animate clip-path: inset(...) ของแต่ละแผ่น. เลื่อนขึ้นก็ย้อนได้ทันที เพราะผูกกับ scrollbar ตรงๆ ไม่ใช่ play ครั้งเดียว.
start/end: ช่วง scroll ที่ effect ทำงานscrub number: หน่วงตาม (เช่น 0.5)gsap.to('.panel',{ clipPath:'inset(0 0 0 0)', ease:'none', scrollTrigger:{ trigger:'.reveal-mask', start:'top 80%', end:'bottom 40%', scrub:true }});
การ์ดเอียงตามเมาส์ในพื้นที่ 3 มิติจริง (rotateX/Y + perspective) พร้อมแสงสะท้อนวิ่งตามมือ — ของพื้นฐานที่ทำให้ "พรีเมียม" ทันที.
map ตำแหน่งเมาส์ในกรอบการ์ด (−0.5…0.5) เป็นองศา rotateX/Y. ป้าย translateZ ลอยขึ้นมาเพื่อ parallax. แสง sheen คือ radial-gradient ที่ย้ายจุดศูนย์กลางตามเมาส์.
maxTilt 14°: ความเอียงสูงสุดperspective 900px: ยิ่งน้อยยิ่งลึกtranslateZ ป้าย: ความ "ลอย"const rx=(.5-py)*14, ry=(px-.5)*14; // py,px = 0..1 ในกรอบการ์ด gsap.to(card,{rotateX:rx,rotateY:ry,duration:.4,ease:'power2.out',transformPerspective:900}); card.style.setProperty('--mx', px*100+'%'); card.style.setProperty('--my', py*100+'%');
หน้าหยุดนิ่ง (pin) แล้วเนื้อหาเลื่อนแนวนอนตามการ scroll แนวตั้ง — คือ "scroll สร้างหนัง" ที่เว็บ storytelling ใช้เล่าเป็นฉากๆ.
pin section ไว้ระหว่างช่วง scroll หนึ่ง แล้วแปลง scroll progress เป็น translateX ลบของ track ให้พอดีกับความกว้างที่ล้นจอ. ผู้ใช้รู้สึกว่า "เลื่อนลง" แต่ภาพ "เลื่อนข้าง".
end:'+=2000': ระยะ scroll ต่อความยาวscrub: หน่วงการตามconst dist = track.scrollWidth - innerWidth; gsap.to(track,{ x:-dist, ease:'none', scrollTrigger:{ trigger:outer, pin:true, scrub:1, start:'top top', end:()=> '+='+dist, invalidateOnRefresh:true }});