AWE · LabSignature Motion System v2
Beyond the reference

คลังเทคนิคระดับ Awwwards เวอร์ชันเจาะลึก — immersive WebGL, custom GLSL, scroll-driven cinema. ทุกชิ้นเล่นจริง พร้อม pause เองเมื่อออกนอกจอ

SCROLL

เราไม่ได้ลอกเทคนิค เราเข้าใจมัน

v1 คือพจนานุกรม v2 คือสตูดิโอ ทุก effect ลงทะเบียนใน registry เดียว, mount แบบ lazy ตอนเข้าใกล้จอ, หยุด render loop อัตโนมัติเมื่อเลื่อนผ่าน, และมี destroy hook — สถาปัตยกรรมเดียวกับเว็บที่ชนะ Awwwards จริง

01 · REGISTRY

AWW.register()

ทุก effect คือ factory คืน {destroy, setActive} ลงทะเบียนด้วยชื่อเดียว

02 · LAZY MOUNT

IntersectionObserver

mount เมื่อเข้าใกล้ viewport 200px ไม่กินทรัพยากรล่วงหน้า

03 · AUTO-PAUSE

setActive(false)

ออกนอกจอ → หยุด rAF/WebGL ทันที ประหยัด GPU + แบต

04 · A11Y

reduced-motion

เคารพ prefers-reduced-motion ทุกชิ้น มี fallback นิ่ง

01

Aurora Plasma

Raw GLSL · Fragment shader

พื้นผิวลื่นไหลที่คำนวณต่อพิกเซลบน GPU ด้วย domain-warped fBm noise — ไม่มี texture ไม่มี mesh มีแค่สมการ. นี่คือหัวใจของ background แบบ Hatom/Active Theory.

Live · fBm + domain warp · ขยับเมาส์เพื่อดันสนาม
ทำไมมันสวย

fBm = ซ้อน noise หลายความถี่ (octaves) ให้ได้รายละเอียดแบบเมฆ. "Domain warp" คือเอา noise ไปบิดพิกัดก่อนสุ่มอีกชั้น (fbm(p + fbm(p))) เกิดลายม้วนเป็นของเหลว.

  • คำนวณบน GPU ทุกพิกเซลพร้อมกัน = 60fps แม้เต็มจอ
  • เมาส์ offset พิกัด noise → สนามไหลตามมือ
  • ไล่สี 3 ชั้นตามค่า noise = ไฟเหนือแบบ aurora
ปุ่มปรับ
  • octaves 4–6: รายละเอียด vs ความเร็ว
  • uTime*0.1: ความเร็วการไหล
  • palette 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.);
}
02

Cursor Distortion · RGB Split

WebGL · Three.js shader

เทคนิคตรงจากภาพ Hatom ที่ส่งมา — เมาส์ลากผ่านแล้วเนื้อหา "บิด" เป็นคลื่น พร้อมแยกช่องสี RGB เกิด chromatic aberration. ทำบน plane เดียวด้วย displacement + color-shift ใน fragment shader.

Live · เลื่อนเมาส์ทับภาพ — ยิ่งใกล้ยิ่งบิด
ทำไมมันล้ำ

ระยะจากเมาส์ (distance(uv,uMouse)) คุม 2 อย่าง: ความแรงคลื่น (sin ตามระยะ+เวลา) และระยะแยกสี. smoothstep ทำให้ effect จางหายนุ่มที่ขอบรัศมี ไม่มีเส้นตัด.

  • R/G/B สุ่ม texture คนละ offset → ขอบสีเหลื่อมแบบเลนส์
  • uHover lerp 0→1 ทำให้ entry/exit ลื่น ไม่กระตุก
  • ใช้ texture อะไรก็ได้ — ที่นี่ generate ด้วย canvas เลย
ปุ่มปรับ
  • ripple amp .02: ความแรงคลื่น
  • freq 30: ความถี่คลื่น
  • shift .012: ระยะแยก RGB
  • lerp .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.);
03

Custom Cursor · Magnetic Pull

Pointer · GSAP lerp

เคอร์เซอร์วงแหวนที่ตามมือแบบหน่วง (ดูทั่วทั้งหน้านี้) + ปุ่ม "แม่เหล็ก" ที่ดูดตัวเองเข้าหาเมาส์เมื่อเข้าใกล้ — ลายเซ็นของเว็บ studio ระดับโลก.

Live · ขยับเข้าใกล้ปุ่ม แล้วสังเกตแรงดูด + เคอร์เซอร์โต
กลไก

เคอร์เซอร์จริงถูกซ่อน แล้ว lerp ตำแหน่ง div ตามเมาส์ทุกเฟรม (x += (mouseX-x)*0.15) เกิดการตามแบบสปริง. Magnetic = วัดระยะเมาส์ถึงจุดกึ่งกลางปุ่ม แล้วเลื่อนปุ่มเข้าหา ~30% ของระยะ.

  • 2 ชั้น: วงแหวนหน่วงมาก + จุดกลางตามติด = มีน้ำหนัก
  • mix-blend-mode:difference ให้เห็นบนทุกพื้นหลัง
  • คืนตำแหน่งด้วย elastic ตอนเมาส์ออก = เด้งน่ารัก
ปุ่มปรับ
  • lerp .15 วงแหวน / .35 จุด: ความหนืด
  • radius ดูด 90px: ระยะเริ่มมีแรง
  • strength .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)'}));
04

Velocity Marquee

Scroll-reactive

แถบข้อความวิ่งวนไม่รู้จบ — แต่เร่งและ เอียง (skew) ตามความเร็วการ scroll ของผู้ใช้ ทำให้หน้ารู้สึก "มีแรงเฉื่อย" เหมือนของจริงมีมวล.

AWEMOTIONAWEMOTIONAWEMOTION
Live · เลื่อนหน้าเร็วๆ แล้วดูแถบเร่ง+เอียง
กลไก

track ถูก loop ด้วย modulo: เลื่อน x ลบเรื่อยๆ แล้ว wrap เมื่อพ้นความกว้างชุดแรก. ความเร็ว scroll (จาก Lenis velocity) บวกเข้า speed ฐาน และ map เป็น skewX — เลื่อนแรง = เอียงมาก.

  • velocity จาก Lenis ราบรื่นกว่า scroll event ดิบ
  • skew clamp ไว้ ±20° กันเพี้ยน
  • คืน skew กลับ 0 ด้วย lerp เมื่อหยุด
ปุ่มปรับ
  • 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)`;
05

Text Decode / Scramble

Char shuffle

ตัวอักษรสุ่มมั่วแล้วค่อยๆ "ถอดรหัส" เป็นคำจริงทีละตัว — ให้ความรู้สึกระบบกำลังประมวลผล. หัวข้อ intro ด้านบนก็ใช้ตัวนี้.

DECODE
Live
กลไก

แต่ละตัวอักษรมี "เวลาเริ่ม" และ "เวลาจบ" ต่างกัน. ก่อนถึงเวลาจบ แสดงอักขระสุ่มจากชุด glyph; เมื่อ progress ผ่านจุดจบ ล็อกเป็นตัวจริง. ไล่จากซ้ายไปขวาด้วยการ stagger เวลาเริ่ม.

  • requestAnimationFrame คุม progress 0→1
  • เก็บข้อความจริงใน data-text เพื่อ screen reader
  • glyph set แบบ "01⌁/\\▓" ให้ลุค cyberpunk
ปุ่มปรับ
  • duration: ความเร็วถอดรหัสรวม
  • ความหนาแน่น stagger: ถอดพร้อมกันหรือไล่ทีละตัว
  • glyph set: เปลี่ยนคาแรกเตอร์
// แต่ละตัวล็อกเป็นตัวจริงเมื่อ 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;
06

Scroll-Scrubbed Mask Reveal

clip-path · scrub

สองแผ่นเปิดสวนทางกันตามการ scroll แบบ scrub (ผูก 1:1 กับ scrollbar) — เทคนิค panel/curtain ที่เว็บ portfolio ใช้คั่น section.

เลื่อนหน้าผ่านบล็อกนี้

AWE

LAB

Live · clip-path ผูกกับ scroll progress
กลไก

ScrollTrigger scrub:true map ตำแหน่ง scroll → progress 0→1 แล้ว animate clip-path: inset(...) ของแต่ละแผ่น. เลื่อนขึ้นก็ย้อนได้ทันที เพราะผูกกับ scrollbar ตรงๆ ไม่ใช่ play ครั้งเดียว.

  • clip-path GPU-composited = ลื่น
  • scrub ทำให้คุมจังหวะด้วยมือผู้ใช้
  • reduced-motion → แสดงผลปลายทางทันที
ปุ่มปรับ
  • start/end: ช่วง scroll ที่ effect ทำงาน
  • scrub number: หน่วงตาม (เช่น 0.5)
  • ทิศ inset: เปิดซ้าย/ขวา/บน/ล่าง
gsap.to('.panel',{ clipPath:'inset(0 0 0 0)', ease:'none',
  scrollTrigger:{ trigger:'.reveal-mask', start:'top 80%', end:'bottom 40%', scrub:true }});
07

3D Tilt · Specular Sheen

Perspective · pointer

การ์ดเอียงตามเมาส์ในพื้นที่ 3 มิติจริง (rotateX/Y + perspective) พร้อมแสงสะท้อนวิ่งตามมือ — ของพื้นฐานที่ทำให้ "พรีเมียม" ทันที.

AWE°
Live · ขยับเมาส์เหนือการ์ด
กลไก

map ตำแหน่งเมาส์ในกรอบการ์ด (−0.5…0.5) เป็นองศา rotateX/Y. ป้าย translateZ ลอยขึ้นมาเพื่อ parallax. แสง sheen คือ radial-gradient ที่ย้ายจุดศูนย์กลางตามเมาส์.

  • perspective ที่ parent = ความลึกของ 3D
  • lerp การหมุนให้ลื่น ไม่ติดเมาส์เป๊ะ
  • reset ด้วย power ease ตอนเมาส์ออก
ปุ่มปรับ
  • 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+'%');
08

Pinned Horizontal Scroll

ScrollTrigger pin

หน้าหยุดนิ่ง (pin) แล้วเนื้อหาเลื่อนแนวนอนตามการ scroll แนวตั้ง — คือ "scroll สร้างหนัง" ที่เว็บ storytelling ใช้เล่าเป็นฉากๆ.

01 · Immerse
02 · React
03 · Distort
04 · Remember
กลไก

pin section ไว้ระหว่างช่วง scroll หนึ่ง แล้วแปลง scroll progress เป็น translateX ลบของ track ให้พอดีกับความกว้างที่ล้นจอ. ผู้ใช้รู้สึกว่า "เลื่อนลง" แต่ภาพ "เลื่อนข้าง".

  • คำนวณระยะเลื่อน = trackWidth − viewportWidth
  • end = ความยาว pin ตามเนื้อหา (ยิ่งยาวยิ่งช้า)
  • invalidateOnRefresh ให้ responsive
ปุ่มปรับ
  • end:'+=2000': ระยะ scroll ต่อความยาว
  • scrub: หน่วงการตาม
  • card width/gap: จำนวนการ์ดต่อจอ
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 }});
AICE Hub🔎 ตรวจ AI ฟรี