การเคลื่อนไหว

ระบบการเคลื่อนไหวขั้นสูงสำหรับการสร้างการเคลื่อนไหวแบบไดนามิก, เชิงพรรณนา, และตามนิพจน์ที่ซับซ้อนเกินกว่าการใช้เพียงคีย์เฟรม

ประเภทของการเคลื่อนไหว

ระบบการเคลื่อนไหวสนับสนุนหลายประเภทนอกเหนือจากคีย์เฟรมแบบดั้งเดิม:

  • นิพจน์: นิพจน์ JavaScript ที่ถูกประเมินต่อเฟรม
  • เชิงพรรณนา: รูปแบบการเคลื่อนไหวเชิงพรรณนาที่สร้างไว้ในตัว
  • ลำดับ: การเคลื่อนไหวตามลำดับที่มีการเปลี่ยนแปลงแบบขั้นตอน
  • เส้นทาง: การเคลื่อนไหวตามเส้นทาง Bezier

การเคลื่อนไหวขั้นสูงเหล่านี้ถูกกำหนดในคุณสมบัติอาร์เรย์ animations ขององค์ประกอบต่างๆ

การเคลื่อนไหวของโหนด vs คีย์เฟรม

การเคลื่อนไหวของโหนด (animations array):

  • การเคลื่อนไหวเชิงพรรณนา, ตามนิพจน์, หรือการติดตามเส้นทาง
  • ถูกประเมินทุกเฟรม
  • มีความสำคัญสูงกว่าคีย์เฟรม
  • สามารถแทนที่ค่าคีย์เฟรมได้

คีย์เฟรม (keyframes array):

  • การทำอินเตอร์โพเลชันของคุณสมบัติอย่างง่ายระหว่างจุดที่กำหนด
  • ความสำคัญต่ำกว่า
  • คาดเดาได้, แก้ไขง่าย

ลำดับความสำคัญของการเคลื่อนไหว

เมื่อมีหลายวิธีการเคลื่อนไหวที่มุ่งเป้าไปยังคุณสมบัติเดียวกัน:

  1. สูงสุด: การเคลื่อนไหวของโหนด (นิพจน์, เชิงพรรณนา, เส้นทาง, ลำดับ)
  2. กลาง: การเคลื่อนไหวแบบคีย์เฟรม
  3. ต่ำสุด: ค่าคุณสมบัติคงที่
ทรัพย์สินประเภทจำเป็นตัวอย่างช่วงค่าคำอธิบาย
typestringtrue-expression | procedural | sequence | pathประเภทของการเคลื่อนไหว
enabledbooleanfalse--ว่าการเคลื่อนไหวมีการใช้งานอยู่หรือไม่
targetstringtrue--เส้นทางของคุณสมบัติที่จะเคลื่อนไหว (เช่น "x", "rotation", "scaleX").
startTimenumberfalse--เวลาที่เริ่มต้นของการเคลื่อนไหวเมื่อเปรียบเทียบกับการเริ่มต้นขององค์ประกอบ, ในหน่วยวินาที
durationnumberfalse--ระยะเวลาของการเคลื่อนไหวในหน่วยวินาที สำหรับการเคลื่อนไหวที่วนซ้ำ, นี่คือระยะเวลาของการวนซ้ำ
loopbooleanfalse--ว่าการเคลื่อนไหววนซ้ำหรือไม่
blendModestringfalse-replace | add | multiplyวิธีการที่การเคลื่อนไหวนี้รวมกับการเคลื่อนไหวอื่น: แทนที่ (เขียนทับ), เพิ่ม (รวม), คูณ (ปรับขนาด)
blendWeightnumberfalse--ความเข้ม/อิทธิพลของการเคลื่อนไหวนี้ (0-1). ใช้สำหรับการผสมผสานการเคลื่อนไหวหลายๆ ตัว

การเคลื่อนไหวตามนิพจน์

นิพจน์เป็นโค้ด JavaScript ที่ถูกประเมินต่อเฟรม โดยมีการเข้าถึงตัวแปรบริบทการเคลื่อนไหว

ตัวแปรบริบทที่มีให้

{
  time: number; // เวลาในปัจจุบัน (วินาที)
  localTime: number; // เวลาเมื่อเปรียบเทียบกับการเริ่มต้นขององค์ประกอบ
  duration: number; // ระยะเวลาขององค์ประกอบ
  progress: number; // ความก้าวหน้าที่ปรับให้เป็นมาตรฐาน (0-1)
  index: number; // ดัชนีขององค์ประกอบในกลุ่ม
  total: number; // จำนวนองค์ประกอบทั้งหมดในกลุ่ม
  fps: number; // เฟรมต่อวินาที
  width: number; // ความกว้างของแคนวาส
  height: number; // ความสูงของแคนวาส
  value: any; // ค่าคุณสมบัติในปัจจุบัน
}

ตัวอย่างนิพจน์

การเคลื่อนไหวแบบกระโดดโดยใช้ฟังก์ชัน Math:

expressionAnimation.json
{
"id": "element-1",
"type": "Image",
"start": 0,
"duration": 5,
"animations": [
  {
    "type": "expression",
    "target": "y",
    "enabled": true,
    "expression": "100 + Math.abs(Math.sin(localTime * 3)) * 200",
    "startTime": 0,
    "duration": 5,
    "loop": true
  }
]
}

นิพจน์ที่ซับซ้อน

รวมหลายเอฟเฟกต์:

complexExpression.json
{
"animations": [
  {
    "type": "expression",
    "target": "rotation",
    "expression": "(localTime * 2 * Math.PI / duration) + Math.sin(localTime * 4) * 0.1",
    "loop": true
  },
  {
    "type": "expression",
    "target": "scaleX",
    "expression": "1 + Math.sin(localTime * 5) * 0.2",
    "loop": true
  }
]
}

การเคลื่อนไหวเชิงพรรณนา

รูปแบบการเคลื่อนไหวที่สร้างไว้ในตัวพร้อมพารามิเตอร์ที่กำหนดค่าได้

ประเภทเชิงพรรณนา

  • wiggle: การสั่นแบบสุ่ม (มีประโยชน์สำหรับเอฟเฟกต์กล้องที่ถือด้วยมือ)
  • wave: การเคลื่อนไหวแบบคลื่นไซน์/โคไซน์
  • bounce: เอฟเฟกต์การกระโดดแบบยืดหยุ่น
  • pulse: การปรับขนาดตามจังหวะ

ตัวอย่าง Wiggle

wiggleAnimation.json
{
"animations": [
  {
    "type": "procedural",
    "proceduralType": "wiggle",
    "target": "rotation",
    "enabled": true,
    "frequency": 2,
    "amplitude": 0.1,
    "loop": true
  }
]
}

การเคลื่อนไหวตามลำดับ

การเคลื่อนไหวแบบขั้นตอนที่เปลี่ยนค่าในช่วงเวลาที่กำหนด

ทรัพย์สินประเภทจำเป็นตัวอย่างช่วงค่าคำอธิบาย
valuesarraytrue--อาร์เรย์ของค่าเพื่อทำขั้นตอน
stepDurationnumbertrue--ระยะเวลาของแต่ละขั้นตอนในหน่วยวินาที
loopbooleanfalse--ว่าจะแสดงผลลัพธ์ในลำดับนี้หรือไม่

ตัวอย่างลำดับ

ข้อความที่หมุนเวียนผ่านขนาดที่แตกต่างกัน:

sequenceAnimation.json
{
"animations": [
  {
    "type": "sequence",
    "target": "fontSize",
    "enabled": true,
    "values": [24, 32, 40, 32, 24],
    "stepDuration": 0.5,
    "loop": true
  }
]
}

การเคลื่อนไหวตามเส้นทาง

การเคลื่อนไหวขององค์ประกอบตามเส้นทางของโค้ง Bezier

ทรัพย์สินประเภทจำเป็นตัวอย่างช่วงค่าคำอธิบาย
pathstring | arraytrue--ข้อมูลเส้นทาง SVG (แอตทริบิวต์ d) หรืออาร์เรย์ของจุดเส้นทาง
autoRotatebooleanfalse--ว่าจะแสดงผลลัพธ์โดยอัตโนมัติเพื่อให้มีการหมุนตามแนวเส้นทางหรือไม่
durationnumbertrue--เวลาที่ใช้ในการทำเส้นทางทั้งหมดให้เสร็จสิ้น

ตัวอย่างเส้นทาง

การเคลื่อนไหวแบบวงกลม:

pathAnimation.json
{
"animations": [
  {
    "type": "path",
    "target": "position",
    "enabled": true,
    "path": "M 100,100 a 200,200 0 1,1 0,1 Z",
    "autoRotate": true,
    "duration": 4,
    "loop": true
  }
]
}

ไวยากรณ์เส้นทางของคุณสมบัติ

คุณสมบัติ target ใช้การตั้งค่าด้วยจุดเพื่อระบุคุณสมบัติที่ซ้อนกัน:

  • ง่าย: "x", "y", "rotation"
  • ซ้อนกัน: "scale.x", "position.y"
  • อาร์เรย์: "colors[0]", "points[2].x"

ตัวอย่างการแก้ไขเป้าหมาย

{
  "target": "x"              // ตำแหน่ง x ขององค์ประกอบ
  "target": "scaleX"         // สเกลแนวนอน
  "target": "alpha"          // ความโปร่งใส
  "target": "fill.color"     // สีเติม (ถ้า fill เป็นออบเจ็กต์)
  "target": "points[0].x"    // พิกัด x ของจุดแรก
}

โหมดการผสม

ควบคุมวิธีการที่การเคลื่อนไหวรวมกันเมื่อหลายตัวมุ่งเป้าไปยังคุณสมบัติเดียวกัน:

แทนที่ (ค่าเริ่มต้น)

{
  "blendMode": "replace",
  "blendWeight": 1.0
}

แทนที่ค่าคุณสมบัติอย่างสมบูรณ์

เพิ่ม

{
  "blendMode": "add",
  "blendWeight": 0.5
}

เพิ่มไปยังค่าที่มีอยู่ (มีน้ำหนัก). มีประโยชน์สำหรับการสะสมค่าที่ต่างกัน

คูณ

{
  "blendMode": "multiply",
  "blendWeight": 1.0
}

คูณค่าที่มีอยู่. มีประโยชน์สำหรับการปรับขนาดเอฟเฟกต์

ตัวอย่างการเคลื่อนไหวหลายประเภท

รวมประเภทการเคลื่อนไหวที่แตกต่างกัน:

multipleAnimations.json
{
"id": "animated-element",
"type": "Shape",
"animations": [
  {
    "type": "expression",
    "target": "x",
    "expression": "width / 2 + Math.sin(localTime * 2) * 300",
    "blendMode": "replace",
    "loop": true
  },
  {
    "type": "procedural",
    "proceduralType": "wiggle",
    "target": "rotation",
    "frequency": 3,
    "amplitude": 0.2,
    "blendMode": "add",
    "blendWeight": 0.5
  },
  {
    "type": "sequence",
    "target": "alpha",
    "values": [1, 0.8, 1],
    "stepDuration": 0.3,
    "loop": true,
    "blendMode": "multiply"
  }
],
"keyframes": [
  {
    "id": "kf-1",
    "time": 0,
    "stateObj": { "scaleX": 1 }
  },
  {
    "id": "kf-2",
    "time": 2,
    "stateObj": { "scaleX": 1.5 }
  }
]
}

ในตัวอย่างนี้:

  • นิพจน์ควบคุมตำแหน่งแนวนอน (โหมดแทนที่)
  • การเคลื่อนไหวเชิงพรรณนา wiggle เพิ่มความแตกต่างของการหมุนเล็กน้อย
  • ลำดับกระตุ้นความโปร่งใส
  • คีย์เฟรมปรับขนาดองค์ประกอบ (ความสำคัญต่ำสุด)

หมายเหตุสำคัญ

Sandbox ของนิพจน์

  • นิพจน์ทำงานในบริบทที่ถูกจำกัดเพื่อความปลอดภัย
  • เข้าถึงตัวแปรบริบทการเคลื่อนไหวเท่านั้น
  • ไม่สามารถเข้าถึง DOM, ไลบรารีภายนอก, หรือ API ของ Node.js
  • ข้อผิดพลาดในการประเมินจะทำให้การเคลื่อนไหวหยุดทำงาน

ประสิทธิภาพ

  • นิพจน์: ถูกประเมินทุกเฟรม - ควรทำให้เรียบง่าย
  • เชิงพรรณนา: ฟังก์ชันในตัวที่ถูกปรับให้เหมาะสม - มีประสิทธิภาพมาก
  • ลำดับ: มีค่าใช้จ่ายน้อย, ดีสำหรับการเปลี่ยนแปลงแบบแยก
  • เส้นทาง: ค่าใช้จ่ายปานกลาง, ขึ้นอยู่กับความซับซ้อนของเส้นทาง

การตั้งเวลา

  • startTime และ duration เป็นสัมพัทธ์กับเวลา start ขององค์ประกอบ
  • localTime ในนิพจน์เริ่มต้นที่ 0 เมื่อองค์ประกอบเริ่มต้น
  • การเคลื่อนไหวเคารพขอบเขต duration โดยรวมขององค์ประกอบ

การดีบัก

  • หากการเคลื่อนไหวไม่ทำงาน, ตรวจสอบ:
    • enabled: true
    • คุณสมบัติ target มีอยู่ในองค์ประกอบ
    • ไวยากรณ์นิพจน์เป็น JavaScript ที่ถูกต้อง
    • startTime อยู่ภายในระยะเวลาขององค์ประกอบ
    • ไม่มีการสะกดผิดในเส้นทางของคุณสมบัติ

กรณีการใช้งานทั่วไป

เอฟเฟกต์การติดตามที่นุ่มนวล

{
  "type": "expression",
  "target": "x",
  "expression": "value + (targetX - value) * 0.1"
}

การกระโดดแบบยืดหยุ่น

{
  "type": "expression",
  "target": "scaleY",
  "expression": "1 + Math.exp(-localTime * 3) * Math.cos(localTime * 8) * 0.5"
}

การสั่นของกล้อง

{
  "type": "procedural",
  "proceduralType": "wiggle",
  "target": "x",
  "frequency": 10,
  "amplitude": 5
}

การเคลื่อนไหวแบบจัดกลุ่ม (ในกลุ่ม)

{
  "type": "expression",
  "target": "alpha",
  "expression": "Math.max(0, Math.min(1, (localTime - index * 0.1) * 2))"
}