🎉 Initial commit
This commit is contained in:
75
animation/blink.go
Normal file
75
animation/blink.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package animation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BlinkAnimation 闪烁动画
|
||||
type BlinkAnimation struct {
|
||||
BaseAnimation
|
||||
BlinkCount int // 闪烁次数
|
||||
MinOpacity float64 // 最小透明度
|
||||
MaxOpacity float64 // 最大透明度
|
||||
}
|
||||
|
||||
// NewBlinkAnimation 创建一个闪烁动画
|
||||
func NewBlinkAnimation(targetID string, minOpacity, maxOpacity float64, blinkCount int, duration float64, repeatCount int) *BlinkAnimation {
|
||||
return &BlinkAnimation{
|
||||
BaseAnimation: BaseAnimation{
|
||||
Type: Blink, // 闪烁动画类型
|
||||
Duration: duration,
|
||||
RepeatCount: repeatCount,
|
||||
Delay: 0,
|
||||
TargetID: targetID,
|
||||
Attributes: make(map[string]string),
|
||||
},
|
||||
BlinkCount: blinkCount,
|
||||
MinOpacity: minOpacity,
|
||||
MaxOpacity: maxOpacity,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSVG 生成闪烁动画的SVG代码
|
||||
func (a *BlinkAnimation) GenerateSVG() string {
|
||||
var sb strings.Builder
|
||||
|
||||
// 创建一个animate元素
|
||||
sb.WriteString(fmt.Sprintf("<animate href=\"#%s\" attributeName=\"opacity\" ", a.TargetID))
|
||||
|
||||
// 根据闪烁次数生成关键帧
|
||||
var keyTimes, values []string
|
||||
|
||||
// 计算关键帧
|
||||
for i := 0; i <= a.BlinkCount*2; i++ {
|
||||
// 计算关键帧时间点
|
||||
keyTime := float64(i) / float64(a.BlinkCount*2)
|
||||
keyTimes = append(keyTimes, fmt.Sprintf("%.2f", keyTime))
|
||||
|
||||
// 计算关键帧值,交替使用最大和最小透明度
|
||||
if i%2 == 0 {
|
||||
values = append(values, fmt.Sprintf("%.1f", a.MaxOpacity))
|
||||
} else {
|
||||
values = append(values, fmt.Sprintf("%.1f", a.MinOpacity))
|
||||
}
|
||||
}
|
||||
|
||||
// 添加关键帧属性
|
||||
sb.WriteString(fmt.Sprintf("keyTimes=\"%s\" ", strings.Join(keyTimes, ";")))
|
||||
sb.WriteString(fmt.Sprintf("values=\"%s\" ", strings.Join(values, ";")))
|
||||
sb.WriteString(fmt.Sprintf("dur=\"%gs\" ", a.Duration))
|
||||
|
||||
if a.RepeatCount < 0 {
|
||||
sb.WriteString("repeatCount=\"indefinite\" ")
|
||||
} else if a.RepeatCount > 0 {
|
||||
sb.WriteString(fmt.Sprintf("repeatCount=\"%d\" ", a.RepeatCount))
|
||||
}
|
||||
|
||||
if a.Delay > 0 {
|
||||
sb.WriteString(fmt.Sprintf("begin=\"%gs\" ", a.Delay))
|
||||
}
|
||||
|
||||
sb.WriteString("/>\n")
|
||||
|
||||
return sb.String()
|
||||
}
|
105
animation/bounce.go
Normal file
105
animation/bounce.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package animation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BounceAnimation 弹跳动画
|
||||
type BounceAnimation struct {
|
||||
BaseAnimation
|
||||
Property string // 要变换的属性(如 y, transform 等)
|
||||
From string // 起始值
|
||||
To string // 结束值
|
||||
BounceCount int // 弹跳次数
|
||||
}
|
||||
|
||||
// NewBounceAnimation 创建一个弹跳动画
|
||||
func NewBounceAnimation(targetID string, property string, from, to string, bounceCount int, duration float64, repeatCount int) *BounceAnimation {
|
||||
return &BounceAnimation{
|
||||
BaseAnimation: BaseAnimation{
|
||||
Type: Bounce, // 弹跳动画类型
|
||||
Duration: duration,
|
||||
RepeatCount: repeatCount,
|
||||
Delay: 0,
|
||||
TargetID: targetID,
|
||||
Attributes: make(map[string]string),
|
||||
},
|
||||
Property: property,
|
||||
From: from,
|
||||
To: to,
|
||||
BounceCount: bounceCount,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSVG 生成弹跳动画的SVG代码
|
||||
func (a *BounceAnimation) GenerateSVG() string {
|
||||
var sb strings.Builder
|
||||
|
||||
// 创建animateTransform元素,使用transform属性
|
||||
if a.Property == "transform" {
|
||||
sb.WriteString(fmt.Sprintf("<animateTransform href=\"#%s\" attributeName=\"transform\" type=\"translate\" ", a.TargetID))
|
||||
sb.WriteString(fmt.Sprintf("from=\"%s\" to=\"%s\" ", a.From, a.To))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("<animate href=\"#%s\" attributeName=\"%s\" ", a.TargetID, a.Property))
|
||||
sb.WriteString(fmt.Sprintf("from=\"%s\" to=\"%s\" ", a.From, a.To))
|
||||
}
|
||||
|
||||
sb.WriteString(fmt.Sprintf("dur=\"%gs\" ", a.Duration))
|
||||
|
||||
// 生成弹跳效果的关键帧
|
||||
var keyTimes, values []string
|
||||
step := 1.0 / float64(a.BounceCount*2)
|
||||
|
||||
for i := 0; i <= a.BounceCount*2; i++ {
|
||||
// 调整关键帧时间,使动画更加平滑
|
||||
keyTime := float64(i) * step
|
||||
// 为每个弹跳周期添加额外的中间帧
|
||||
if i > 0 && i < a.BounceCount*2 {
|
||||
keyTime = keyTime + (step * 0.1) // 稍微延长每次弹跳的时间
|
||||
}
|
||||
keyTimes = append(keyTimes, fmt.Sprintf("%.3f", keyTime))
|
||||
|
||||
if i%2 == 0 {
|
||||
values = append(values, a.From)
|
||||
} else {
|
||||
values = append(values, a.To)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加关键帧属性
|
||||
sb.WriteString(fmt.Sprintf("values=\"%s\" ", strings.Join(values, ";")))
|
||||
sb.WriteString(fmt.Sprintf("keyTimes=\"%s\" ", strings.Join(keyTimes, ";")))
|
||||
|
||||
// 添加缓动函数
|
||||
sb.WriteString("calcMode=\"spline\" ")
|
||||
sb.WriteString("keySplines=\"")
|
||||
for i := 0; i < len(values)-1; i++ {
|
||||
if i > 0 {
|
||||
sb.WriteString(";")
|
||||
}
|
||||
if i%2 == 0 {
|
||||
// 快速上升
|
||||
sb.WriteString("0.2 0 0.8 1")
|
||||
} else {
|
||||
// 缓慢下落
|
||||
sb.WriteString("0.2 0.8 0.8 1")
|
||||
}
|
||||
}
|
||||
sb.WriteString("\" ")
|
||||
|
||||
if a.RepeatCount < 0 {
|
||||
sb.WriteString("repeatCount=\"indefinite\" ")
|
||||
} else if a.RepeatCount > 0 {
|
||||
sb.WriteString(fmt.Sprintf("repeatCount=\"%d\" ", a.RepeatCount))
|
||||
}
|
||||
|
||||
if a.Delay > 0 {
|
||||
sb.WriteString(fmt.Sprintf("begin=\"%gs\" ", a.Delay))
|
||||
}
|
||||
|
||||
// 添加fill属性
|
||||
sb.WriteString("fill=\"freeze\" />")
|
||||
|
||||
return sb.String()
|
||||
}
|
55
animation/color.go
Normal file
55
animation/color.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package animation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ColorAnimation 颜色变换动画
|
||||
type ColorAnimation struct {
|
||||
BaseAnimation
|
||||
FromColor string // 起始颜色
|
||||
ToColor string // 结束颜色
|
||||
Property string // 要变换的属性(fill 或 stroke)
|
||||
}
|
||||
|
||||
// NewColorAnimation 创建一个颜色变换动画
|
||||
func NewColorAnimation(targetID string, property string, fromColor, toColor string, duration float64, repeatCount int) *ColorAnimation {
|
||||
return &ColorAnimation{
|
||||
BaseAnimation: BaseAnimation{
|
||||
Type: Color, // 颜色变换动画类型
|
||||
Duration: duration,
|
||||
RepeatCount: repeatCount,
|
||||
Delay: 0,
|
||||
TargetID: targetID,
|
||||
Attributes: make(map[string]string),
|
||||
},
|
||||
FromColor: fromColor,
|
||||
ToColor: toColor,
|
||||
Property: property,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSVG 生成颜色变换动画的SVG代码
|
||||
func (a *ColorAnimation) GenerateSVG() string {
|
||||
var sb strings.Builder
|
||||
|
||||
// 创建一个animate元素
|
||||
sb.WriteString(fmt.Sprintf("<animate href=\"#%s\" attributeName=\"%s\" ", a.TargetID, a.Property))
|
||||
sb.WriteString(fmt.Sprintf("from=\"%s\" to=\"%s\" ", a.FromColor, a.ToColor))
|
||||
sb.WriteString(fmt.Sprintf("dur=\"%gs\" ", a.Duration))
|
||||
|
||||
if a.RepeatCount < 0 {
|
||||
sb.WriteString("repeatCount=\"indefinite\" ")
|
||||
} else if a.RepeatCount > 0 {
|
||||
sb.WriteString(fmt.Sprintf("repeatCount=\"%d\" ", a.RepeatCount))
|
||||
}
|
||||
|
||||
if a.Delay > 0 {
|
||||
sb.WriteString(fmt.Sprintf("begin=\"%gs\" ", a.Delay))
|
||||
}
|
||||
|
||||
sb.WriteString("/>\n")
|
||||
|
||||
return sb.String()
|
||||
}
|
50
animation/fade.go
Normal file
50
animation/fade.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package animation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// FadeAnimation 淡入淡出动画
|
||||
type FadeAnimation struct {
|
||||
BaseAnimation
|
||||
From string // 起始透明度
|
||||
To string // 结束透明度
|
||||
}
|
||||
|
||||
// NewFadeAnimation 创建一个淡入淡出动画
|
||||
func NewFadeAnimation(targetID string, from, to string, duration float64, repeatCount int) *FadeAnimation {
|
||||
return &FadeAnimation{
|
||||
BaseAnimation: BaseAnimation{
|
||||
Type: Fade,
|
||||
Duration: duration,
|
||||
RepeatCount: repeatCount,
|
||||
Delay: 0,
|
||||
TargetID: targetID,
|
||||
Attributes: make(map[string]string),
|
||||
},
|
||||
From: from,
|
||||
To: to,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSVG 生成淡入淡出动画的SVG代码
|
||||
func (a *FadeAnimation) GenerateSVG() string {
|
||||
// 创建一个animate元素,并将其添加到目标元素中
|
||||
svg := fmt.Sprintf("<animate href=\"#%s\" attributeName=\"opacity\" ", a.TargetID)
|
||||
svg += fmt.Sprintf("from=\"%s\" to=\"%s\" ", a.From, a.To)
|
||||
svg += fmt.Sprintf("dur=\"%gs\" ", a.Duration)
|
||||
|
||||
if a.RepeatCount < 0 {
|
||||
svg += "repeatCount=\"indefinite\" "
|
||||
} else if a.RepeatCount > 0 {
|
||||
svg += fmt.Sprintf("repeatCount=\"%d\" ", a.RepeatCount)
|
||||
}
|
||||
|
||||
if a.Delay > 0 {
|
||||
svg += fmt.Sprintf("begin=\"%gs\" ", a.Delay)
|
||||
}
|
||||
|
||||
svg += "/>\n"
|
||||
|
||||
return svg
|
||||
}
|
73
animation/gradient.go
Normal file
73
animation/gradient.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package animation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GradientAnimation 渐变动画
|
||||
type GradientAnimation struct {
|
||||
BaseAnimation
|
||||
Colors []string // 渐变颜色列表
|
||||
Animate bool // 是否添加动画效果
|
||||
}
|
||||
|
||||
// NewGradientAnimation 创建一个渐变动画
|
||||
func NewGradientAnimation(targetID string, colors []string, duration float64, repeatCount int, animate bool) *GradientAnimation {
|
||||
return &GradientAnimation{
|
||||
BaseAnimation: BaseAnimation{
|
||||
Type: Gradient,
|
||||
Duration: duration,
|
||||
RepeatCount: repeatCount,
|
||||
Delay: 0,
|
||||
TargetID: targetID,
|
||||
Attributes: make(map[string]string),
|
||||
},
|
||||
Colors: colors,
|
||||
Animate: animate,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSVG 生成渐变动画的SVG代码
|
||||
func (a *GradientAnimation) GenerateSVG() string {
|
||||
var sb strings.Builder
|
||||
|
||||
// 创建渐变定义
|
||||
gradientID := fmt.Sprintf("%s-gradient", a.TargetID)
|
||||
sb.WriteString(fmt.Sprintf("<linearGradient id=\"%s\" x1=\"0%%\" y1=\"0%%\" x2=\"100%%\" y2=\"0%%\">\n", gradientID))
|
||||
|
||||
// 添加渐变颜色
|
||||
for i, color := range a.Colors {
|
||||
offset := float64(i) / float64(len(a.Colors)-1) * 100
|
||||
sb.WriteString(fmt.Sprintf(" <stop offset=\"%g%%\" stop-color=\"%s\" />\n", offset, color))
|
||||
}
|
||||
sb.WriteString("</linearGradient>\n")
|
||||
|
||||
// 为目标元素添加样式引用
|
||||
sb.WriteString(fmt.Sprintf("<style type=\"text/css\">\n #%s { fill: url(#%s) !important; }\n</style>\n", a.TargetID, gradientID))
|
||||
|
||||
// 添加动画
|
||||
if a.Animate {
|
||||
// x1 动画
|
||||
sb.WriteString(fmt.Sprintf("<animate href=\"#%s\" attributeName=\"x1\" from=\"0%%\" to=\"100%%\" ", gradientID))
|
||||
sb.WriteString(fmt.Sprintf("dur=\"%gs\" ", a.Duration))
|
||||
if a.RepeatCount < 0 {
|
||||
sb.WriteString("repeatCount=\"indefinite\" ")
|
||||
} else if a.RepeatCount > 0 {
|
||||
sb.WriteString(fmt.Sprintf("repeatCount=\"%d\" ", a.RepeatCount))
|
||||
}
|
||||
sb.WriteString("/>\n")
|
||||
|
||||
// x2 动画
|
||||
sb.WriteString(fmt.Sprintf("<animate href=\"#%s\" attributeName=\"x2\" from=\"100%%\" to=\"200%%\" ", gradientID))
|
||||
sb.WriteString(fmt.Sprintf("dur=\"%gs\" ", a.Duration))
|
||||
if a.RepeatCount < 0 {
|
||||
sb.WriteString("repeatCount=\"indefinite\" ")
|
||||
} else if a.RepeatCount > 0 {
|
||||
sb.WriteString(fmt.Sprintf("repeatCount=\"%d\" ", a.RepeatCount))
|
||||
}
|
||||
sb.WriteString("/>\n")
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
62
animation/path.go
Normal file
62
animation/path.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package animation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PathAnimation 路径动画
|
||||
type PathAnimation struct {
|
||||
BaseAnimation
|
||||
Path string // SVG路径数据
|
||||
Rotate string // 是否旋转元素以跟随路径方向 ("auto", "auto-reverse", 或 "0")
|
||||
}
|
||||
|
||||
// NewPathAnimation 创建一个路径动画
|
||||
func NewPathAnimation(targetID string, path string, duration float64, repeatCount int) *PathAnimation {
|
||||
return &PathAnimation{
|
||||
BaseAnimation: BaseAnimation{
|
||||
Type: Path, // 路径动画类型
|
||||
Duration: duration,
|
||||
RepeatCount: repeatCount,
|
||||
Delay: 0,
|
||||
TargetID: targetID,
|
||||
Attributes: make(map[string]string),
|
||||
},
|
||||
Path: path,
|
||||
Rotate: "0", // 默认不旋转
|
||||
}
|
||||
}
|
||||
|
||||
// WithRotate 设置是否旋转元素以跟随路径方向
|
||||
func (a *PathAnimation) WithRotate(rotate string) *PathAnimation {
|
||||
a.Rotate = rotate
|
||||
return a
|
||||
}
|
||||
|
||||
// GenerateSVG 生成路径动画的SVG代码
|
||||
func (a *PathAnimation) GenerateSVG() string {
|
||||
var sb strings.Builder
|
||||
|
||||
// 创建一个animateMotion元素
|
||||
sb.WriteString(fmt.Sprintf("<animateMotion href=\"#%s\" ", a.TargetID))
|
||||
sb.WriteString(fmt.Sprintf("path=\"%s\" ", a.Path))
|
||||
sb.WriteString(fmt.Sprintf("dur=\"%gs\" ", a.Duration))
|
||||
|
||||
if a.RepeatCount < 0 {
|
||||
sb.WriteString("repeatCount=\"indefinite\" ")
|
||||
} else if a.RepeatCount > 0 {
|
||||
sb.WriteString(fmt.Sprintf("repeatCount=\"%d\" ", a.RepeatCount))
|
||||
}
|
||||
|
||||
if a.Delay > 0 {
|
||||
sb.WriteString(fmt.Sprintf("begin=\"%gs\" ", a.Delay))
|
||||
}
|
||||
|
||||
// 设置旋转属性
|
||||
sb.WriteString(fmt.Sprintf("rotate=\"%s\" ", a.Rotate))
|
||||
|
||||
sb.WriteString("/>\n")
|
||||
|
||||
return sb.String()
|
||||
}
|
61
animation/rotate.go
Normal file
61
animation/rotate.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package animation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// RotateAnimation 旋转动画
|
||||
type RotateAnimation struct {
|
||||
BaseAnimation
|
||||
FromAngle float64 // 起始角度
|
||||
ToAngle float64 // 结束角度
|
||||
CenterX float64 // 旋转中心X坐标
|
||||
CenterY float64 // 旋转中心Y坐标
|
||||
}
|
||||
|
||||
// NewRotateAnimation 创建一个旋转动画
|
||||
func NewRotateAnimation(targetID string, fromAngle, toAngle float64, duration float64, repeatCount int) *RotateAnimation {
|
||||
return &RotateAnimation{
|
||||
BaseAnimation: BaseAnimation{
|
||||
Type: Rotate,
|
||||
Duration: duration,
|
||||
RepeatCount: repeatCount,
|
||||
Delay: 0,
|
||||
TargetID: targetID,
|
||||
Attributes: make(map[string]string),
|
||||
},
|
||||
FromAngle: fromAngle,
|
||||
ToAngle: toAngle,
|
||||
CenterX: 0,
|
||||
CenterY: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSVG 生成旋转动画的SVG代码
|
||||
func (a *RotateAnimation) GenerateSVG() string {
|
||||
// 创建一个带有transform-box和transform-origin样式的g元素
|
||||
// 这个g元素将包裹目标元素及其相关元素
|
||||
svg := fmt.Sprintf("<g style=\"transform-box: fill-box; transform-origin: center;\">\n")
|
||||
|
||||
// 这里只添加animateTransform元素
|
||||
svg += fmt.Sprintf(" <animateTransform attributeName=\"transform\" attributeType=\"XML\" type=\"rotate\" ")
|
||||
// 不再需要指定中心点坐标,直接使用角度值
|
||||
svg += fmt.Sprintf("from=\"%g\" to=\"%g\" ", a.FromAngle, a.ToAngle)
|
||||
svg += fmt.Sprintf("dur=\"%gs\" ", a.Duration)
|
||||
|
||||
if a.RepeatCount < 0 {
|
||||
svg += "repeatCount=\"indefinite\" "
|
||||
} else if a.RepeatCount > 0 {
|
||||
svg += fmt.Sprintf("repeatCount=\"%d\" ", a.RepeatCount)
|
||||
}
|
||||
|
||||
if a.Delay > 0 {
|
||||
svg += fmt.Sprintf("begin=\"%gs\" ", a.Delay)
|
||||
}
|
||||
|
||||
svg += "additive=\"sum\" />\n"
|
||||
|
||||
svg += "</g>\n"
|
||||
|
||||
return svg
|
||||
}
|
52
animation/transform.go
Normal file
52
animation/transform.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package animation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// TransformAnimation 变换动画
|
||||
type TransformAnimation struct {
|
||||
BaseAnimation
|
||||
TransformType string // 变换类型(scale, translate等)
|
||||
From string // 起始变换值
|
||||
To string // 结束变换值
|
||||
}
|
||||
|
||||
// NewTransformAnimation 创建一个变换动画
|
||||
func NewTransformAnimation(targetID string, transformType string, from, to string, duration float64, repeatCount int) *TransformAnimation {
|
||||
return &TransformAnimation{
|
||||
BaseAnimation: BaseAnimation{
|
||||
Type: Transform,
|
||||
Duration: duration,
|
||||
RepeatCount: repeatCount,
|
||||
Delay: 0,
|
||||
TargetID: targetID,
|
||||
Attributes: make(map[string]string),
|
||||
},
|
||||
TransformType: transformType,
|
||||
From: from,
|
||||
To: to,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSVG 生成变换动画的SVG代码
|
||||
func (a *TransformAnimation) GenerateSVG() string {
|
||||
// 创建一个animateTransform元素,并将其添加到目标元素中
|
||||
svg := fmt.Sprintf("<animateTransform href=\"#%s\" attributeName=\"transform\" attributeType=\"XML\" type=\"%s\" ", a.TargetID, a.TransformType)
|
||||
svg += fmt.Sprintf("from=\"%s\" to=\"%s\" ", a.From, a.To)
|
||||
svg += fmt.Sprintf("dur=\"%gs\" ", a.Duration)
|
||||
|
||||
if a.RepeatCount < 0 {
|
||||
svg += "repeatCount=\"indefinite\" "
|
||||
} else if a.RepeatCount > 0 {
|
||||
svg += fmt.Sprintf("repeatCount=\"%d\" ", a.RepeatCount)
|
||||
}
|
||||
|
||||
if a.Delay > 0 {
|
||||
svg += fmt.Sprintf("begin=\"%gs\" ", a.Delay)
|
||||
}
|
||||
|
||||
svg += "additive=\"sum\" />\n"
|
||||
|
||||
return svg
|
||||
}
|
125
animation/types.go
Normal file
125
animation/types.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package animation
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AnimationType 表示动画类型
|
||||
type AnimationType string
|
||||
|
||||
// 预定义动画类型常量
|
||||
const (
|
||||
Rotate AnimationType = "rotate" // 旋转动画
|
||||
Gradient AnimationType = "gradient" // 渐变动画
|
||||
Transform AnimationType = "transform" // 变换动画
|
||||
Fade AnimationType = "fade" // 淡入淡出动画
|
||||
Path AnimationType = "path" // 路径动画
|
||||
Color AnimationType = "color" // 颜色变换动画
|
||||
Bounce AnimationType = "bounce" // 弹跳动画
|
||||
Wave AnimationType = "wave" // 波浪动画
|
||||
Blink AnimationType = "blink" // 闪烁动画
|
||||
)
|
||||
|
||||
// Animation 表示一个SVG动画接口
|
||||
type Animation interface {
|
||||
// GenerateSVG 生成动画的SVG代码
|
||||
GenerateSVG() string
|
||||
// GetTargetID 获取目标元素ID
|
||||
GetTargetID() string
|
||||
}
|
||||
|
||||
// BaseAnimation 基础动画结构,包含所有动画共有的属性
|
||||
type BaseAnimation struct {
|
||||
Type AnimationType // 动画类型
|
||||
Duration float64 // 动画持续时间(秒)
|
||||
RepeatCount int // 重复次数,-1表示无限重复
|
||||
Delay float64 // 延迟时间(秒)
|
||||
TargetID string // 目标元素ID
|
||||
Attributes map[string]string // 动画属性
|
||||
}
|
||||
|
||||
// GetTargetID 获取目标元素ID
|
||||
func (a *BaseAnimation) GetTargetID() string {
|
||||
return a.TargetID
|
||||
}
|
||||
|
||||
// Manager 动画管理器,负责管理所有动画
|
||||
type Manager struct {
|
||||
animations []Animation
|
||||
}
|
||||
|
||||
// GetAnimations 获取所有动画
|
||||
func (m *Manager) GetAnimations() []Animation {
|
||||
return m.animations
|
||||
}
|
||||
|
||||
// NewAnimationManager 创建一个新的动画管理器
|
||||
func NewAnimationManager() *Manager {
|
||||
return &Manager{
|
||||
animations: make([]Animation, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// AddAnimation 添加一个动画
|
||||
func (m *Manager) AddAnimation(animation Animation) {
|
||||
m.animations = append(m.animations, animation)
|
||||
}
|
||||
|
||||
// GenerateSVGAnimations 生成SVG动画代码
|
||||
func (m *Manager) GenerateSVGAnimations() string {
|
||||
if len(m.animations) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
|
||||
// 添加SVG命名空间声明
|
||||
sb.WriteString("<defs>\n")
|
||||
|
||||
// 用于存储需要放在defs中的定义
|
||||
var defsContent strings.Builder
|
||||
// 用于存储需要直接添加到SVG中的动画元素
|
||||
var animationsContent strings.Builder
|
||||
// 用于存储旋转动画的映射,键为目标元素ID
|
||||
rotateAnimations := make(map[string]string)
|
||||
|
||||
// 处理所有动画
|
||||
for _, anim := range m.animations {
|
||||
svgCode := anim.GenerateSVG()
|
||||
if svgCode == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// 根据动画类型决定放置位置
|
||||
switch a := anim.(type) {
|
||||
case *GradientAnimation:
|
||||
// 渐变定义需要放在defs中
|
||||
defsContent.WriteString(svgCode)
|
||||
case *RotateAnimation:
|
||||
// 旋转动画需要包裹目标元素,先存储起来
|
||||
// 提取animateTransform标签
|
||||
if start := strings.Index(svgCode, "<animateTransform"); start != -1 {
|
||||
if end := strings.Index(svgCode[start:], "/>"); end != -1 {
|
||||
rotateAnimations[a.GetTargetID()] = svgCode[start : start+end+2]
|
||||
}
|
||||
}
|
||||
default:
|
||||
// 其他动画元素直接添加到SVG中
|
||||
animationsContent.WriteString(svgCode)
|
||||
}
|
||||
}
|
||||
|
||||
// 只有当存在需要放在defs中的内容时才添加defs标签
|
||||
if defsContent.Len() > 0 {
|
||||
sb.WriteString(defsContent.String())
|
||||
sb.WriteString("</defs>\n")
|
||||
} else {
|
||||
// 如果没有需要放在defs中的内容,则不添加defs标签
|
||||
sb.Reset()
|
||||
}
|
||||
|
||||
// 添加直接放置的动画元素
|
||||
sb.WriteString(animationsContent.String())
|
||||
|
||||
return sb.String()
|
||||
}
|
81
animation/wave.go
Normal file
81
animation/wave.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package animation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// WaveAnimation 波浪动画
|
||||
type WaveAnimation struct {
|
||||
BaseAnimation
|
||||
Amplitude float64 // 波浪振幅
|
||||
Frequency float64 // 波浪频率
|
||||
Direction string // 波浪方向 ("horizontal" 或 "vertical")
|
||||
}
|
||||
|
||||
// NewWaveAnimation 创建一个波浪动画
|
||||
func NewWaveAnimation(targetID string, amplitude, frequency float64, direction string, duration float64, repeatCount int) *WaveAnimation {
|
||||
return &WaveAnimation{
|
||||
BaseAnimation: BaseAnimation{
|
||||
Type: Wave, // 波浪动画类型
|
||||
Duration: duration,
|
||||
RepeatCount: repeatCount,
|
||||
Delay: 0,
|
||||
TargetID: targetID,
|
||||
Attributes: make(map[string]string),
|
||||
},
|
||||
Amplitude: amplitude,
|
||||
Frequency: frequency,
|
||||
Direction: direction,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSVG 生成波浪动画的SVG代码
|
||||
func (a *WaveAnimation) GenerateSVG() string {
|
||||
var sb strings.Builder
|
||||
|
||||
// 生成波浪路径
|
||||
path := a.generateWavePath()
|
||||
|
||||
// 创建一个animateMotion元素
|
||||
sb.WriteString(fmt.Sprintf("<animateMotion href=\"#%s\" ", a.TargetID))
|
||||
sb.WriteString(fmt.Sprintf("path=\"%s\" ", path))
|
||||
sb.WriteString(fmt.Sprintf("dur=\"%gs\" ", a.Duration))
|
||||
|
||||
if a.RepeatCount < 0 {
|
||||
sb.WriteString("repeatCount=\"indefinite\" ")
|
||||
} else if a.RepeatCount > 0 {
|
||||
sb.WriteString(fmt.Sprintf("repeatCount=\"%d\" ", a.RepeatCount))
|
||||
}
|
||||
|
||||
if a.Delay > 0 {
|
||||
sb.WriteString(fmt.Sprintf("begin=\"%gs\" ", a.Delay))
|
||||
}
|
||||
|
||||
sb.WriteString("/>\n")
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// generateWavePath 生成波浪路径
|
||||
func (a *WaveAnimation) generateWavePath() string {
|
||||
var path strings.Builder
|
||||
path.WriteString("M0,0 ")
|
||||
|
||||
// 生成正弦波路径
|
||||
points := 20 // 路径点数量
|
||||
for i := 0; i <= points; i++ {
|
||||
x := float64(i) / float64(points) * 100 // 0-100 范围
|
||||
// 计算正弦波 y 值
|
||||
y := a.Amplitude * math.Sin(a.Frequency*x*math.Pi/180)
|
||||
|
||||
if a.Direction == "horizontal" {
|
||||
path.WriteString(fmt.Sprintf("L%g,%g ", x, y))
|
||||
} else { // vertical
|
||||
path.WriteString(fmt.Sprintf("L%g,%g ", y, x))
|
||||
}
|
||||
}
|
||||
|
||||
return path.String()
|
||||
}
|
Reference in New Issue
Block a user