{"id":29363075,"url":"https://github.com/dankinsoid/vdanimation","last_synced_at":"2025-07-09T09:38:37.658Z","repository":{"id":56925566,"uuid":"209785024","full_name":"dankinsoid/VDAnimation","owner":"dankinsoid","description":"Declarative way to create animations","archived":false,"fork":false,"pushed_at":"2025-04-16T21:38:27.000Z","size":31313,"stargazers_count":28,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-18T14:42:53.114Z","etag":null,"topics":["animation","ios","swift","swiftui","transitions","uikit"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dankinsoid.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"dankinsoid","open_collective":"voidilov-daniil","ko_fi":"dankinsoid","custom":["https://paypal.me/voidilovuae"]}},"created_at":"2019-09-20T12:25:11.000Z","updated_at":"2025-06-05T10:21:48.000Z","dependencies_parsed_at":"2025-04-12T09:26:19.582Z","dependency_job_id":"876212ab-6ef2-4a57-bcd7-e26b9e61fad1","html_url":"https://github.com/dankinsoid/VDAnimation","commit_stats":null,"previous_names":[],"tags_count":93,"template":false,"template_full_name":null,"purl":"pkg:github/dankinsoid/VDAnimation","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dankinsoid%2FVDAnimation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dankinsoid%2FVDAnimation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dankinsoid%2FVDAnimation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dankinsoid%2FVDAnimation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dankinsoid","download_url":"https://codeload.github.com/dankinsoid/VDAnimation/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dankinsoid%2FVDAnimation/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264432172,"owners_count":23607387,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["animation","ios","swift","swiftui","transitions","uikit"],"created_at":"2025-07-09T09:38:36.763Z","updated_at":"2025-07-09T09:38:37.649Z","avatar_url":"https://github.com/dankinsoid.png","language":"Swift","funding_links":["https://github.com/sponsors/dankinsoid","https://opencollective.com/voidilov-daniil","https://ko-fi.com/dankinsoid","https://paypal.me/voidilovuae"],"categories":[],"sub_categories":[],"readme":"# VDAnimation\n\n## Declarative Animations for SwiftUI\n\nVDAnimation provides a powerful, declarative way to create complex animations in Swift with minimal code. Compose animations sequentially, in parallel, with custom timing and curves.\n\n## Features\n\n- 🎭 Declarative animation composition\n- 🔄 Sequence and parallel animations\n- 🎚 Interactive animation control\n- 🎯 Side effects\n- 🏗 Built-in support for custom value interpolation\n- 📱 Working on iOS 13 and macOS 10.15 or later\n- 🧩 SwiftUI and UIKit support\n\n## Examples\n\n### Animating Complex Types\n\n\u003cimg src=\"https://github.com/dankinsoid/Resources/blob/main/VDAnimation/loader.gif?raw=true\" width=\"100\"\u003e\n\n```swift\nstruct LoaderAnimation: View {\n\n    @MotionState private var state = Tween(0.0, 0.01)\n    private let arcSize = 0.4\n\n    var body: some View {\n        WithMotion(_state) { value in\n            Circle()\n                .trim(from: value.start, to: value.end)\n                .stroke(\n                    Color.white,\n                    style: StrokeStyle(lineWidth: 8, lineCap: .round)\n                )\n                .frame(width: 50, height: 50)\n                .rotationEffect(-.degrees(90), anchor: .center)\n                .frame(maxWidth: .infinity, maxHeight: .infinity)\n                .background(Color.green)\n        } motion: {\n            Sequential {\n                Parallel()\n                    .end(arcSize) // animate .end property of the state\n                    .curve(.cubicEaseIn)\n    \n                To(Tween(1 - arcSize, 1.0)) // animate the whole state\n                    .duration(.relative((1 - arcSize) / (1 + arcSize))) // compute duration to keep movement speed constant\n    \n                Parallel()\n                    .start(1.0 - 0.01) // animate .start property of the state\n                    .curve(.cubicEaseOut)\n            }\n            .duration(1)\n            .sync() // synchronize all loaders across the app\n        }\n        .onAppear {\n            $state.play(repeat: true)\n        }\n    }\n}\n```\n\n### Animating Collections\n\n\u003cimg src=\"https://github.com/dankinsoid/Resources/blob/main/VDAnimation/dots.gif?raw=true\" width=\"100\"\u003e\n\n```swift\nstruct DotsAnimation: View {\n\n    @MotionState private var values: [CGFloat] = [0, 0, 0]\n\n    var body: some View {\n        WithMotion(_values) { values in\n            HStack(spacing: 12) {\n                ForEach(Array(values.enumerated()), id: \\.offset) { value in\n                    Circle()\n                        .fill(.white)\n                        .frame(width: 12, height: 12)\n                        .offset(y: value.element)\n                }\n            }\n        } motion: {\n            Parallel { index in\n                To(-10)\n                    .duration(0.3)\n                    .curve(.easeInOut)\n                    .autoreverse()\n                    .delay(.relative(Double(index) / Double(values.count * 2 - 1)))\n            }\n            .sync() // synchronize all loaders across the app\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n        .background(Color.blue)\n        .onAppear {\n            $values.play(repeat: true)\n        }\n    }\n}\n```\n\n### Animating Paths Morphing\n\n\u003cimg src=\"https://github.com/dankinsoid/Resources/blob/main/VDAnimation/path.gif?raw=true\" width=\"100\"\u003e\n\n```swift\nstruct PathAnimation: View {\n    @MotionState var path: Path = Self.heartPath\n\n    var body: some View {\n        VStack {\n            WithMotion(_path) { path in\n                path.fill()\n            } motion: {\n                Sequential {\n                    Wait()\n                    To(Self.dropPath)\n                    Wait()\n                    To(Self.starPath)\n                    Wait()\n                    To(Self.heartPath)\n                }\n                .duration(2)\n            }\n            .frame(width: 100, height: 100)\n            .frame(maxWidth: .infinity, maxHeight: .infinity)\n            .background(Color.purple)\n            .foregroundColor(.white)\n        }\n        .onAppear {\n            $path.play(repeat: true)\n        }\n    }\n}\n```\n\n### Interactive Animation Control\n\n\u003cimg src=\"https://github.com/dankinsoid/Resources/blob/main/VDAnimation/interactive.gif?raw=true\" height=\"120\"\u003e\n\n```swift\nstruct InteractiveAnimation: View {\n\n    @MotionState private var animation = Props()\n    \n    @Tweenable\n    struct Props {\n        var color: Color = .red\n        var angle: Angle = .zero\n        var offset: CGFloat = -120\n    }\n    \n    var body: some View {\n        VStack(spacing: 0) {\n            WithMotion(_animation) { props in\n                VStack(spacing: 10) {\n                    Rectangle()\n                        .fill(props.color)\n                        .rotationEffect(props.angle, anchor: .center)\n                        .offset(x: props.offset)\n                        .frame(width: 100, height: 100)\n                    Slider(value: _animation.$progress, in: 0...1)\n                        .padding(.horizontal)\n                }\n            } motion: {\n                To(\n                    Props(\n                        color: .blue,\n                        angle: .degrees(360),\n                        offset: 120\n                    )\n                )\n                .duration(2.0)\n            }\n            \n            if $animation.isAnimating {\n                Button(\"Pause\") { $animation.pause() }\n            } else {\n                Button(\"Play\") {\n                    if $animation.progress == 1.0 || $animation.progress == 0.0 {\n                        $animation.reverse()\n                    } else {\n                        $animation.play()\n                    }\n                }\n            }\n        }\n    }\n}\n```\n\n### Complex movement\n\n\u003cimg src=\"https://github.com/dankinsoid/Resources/blob/main/VDAnimation/movement.gif?raw=true\" height=\"100\"\u003e\n\n```swift\nstruct ComplexMovement: View {\n\n    @MotionState var location = CGPoint(x: -100, y: 0)\n\n    var body: some View {\n        Circle()\n            .fill(Color.white)\n            .withMotion(_location) {\n                $0.position($1)\n            } motion: {\n                Lerp { t in\n                    CGPoint(\n                        x: cos(Double.lerp(0, .pi * 2, t)) * 100,\n                        y: sin(Double.lerp(0, .pi * 6, t)) * 40\n                    )\n                }\n                .duration(2)\n            }\n            .offset(y: 10)\n            .frame(width: 40, height: 40)\n            .frame(maxWidth: .infinity, maxHeight: .infinity)\n            .background(Color.red)\n            .onTapGesture {\n                if $location.isAnimating {\n                    $location.stop()\n                } else {\n                    $location.play(repeat: true)\n                }\n            }\n    }\n}\n```\n\n### UIKit CADisplayLink wrapper\n\n\u003cimg src=\"https://github.com/dankinsoid/Resources/blob/main/VDAnimation/uikit.gif?raw=true\" height=\"80\"\u003e\n\n```swift\nmotionDisplayLink(Value(amount: 0, color: .systemRed)) { [label] value in\n    label.text = \"\\(value.amount) USD\"\n    label.textColor = value.color\n} motion: {\n    To(Value(amount: 1000, color: .systemGreen))\n        .delay(.relative(0.2))\n        .delayAfter(.relative(0.2))\n        .duration(2)\n}\n.play()\n```\n\n## Usage\n\n### [Motion Guide](MOTION_GUIDE.md)\n\n## Installation\n\n### Swift Package Manager\n\nAdd the package dependency to your `Package.swift`:\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/dankinsoid/VDAnimation.git\", from: \"2.3.0\")\n]\n```\n\nOr add it directly in Xcode via File \u003e Add Packages...\n\n## License\n\nVDAnimation is available under the MIT license. See the LICENSE file for more info.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdankinsoid%2Fvdanimation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdankinsoid%2Fvdanimation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdankinsoid%2Fvdanimation/lists"}