wake-ios/wake/View/Blind/Box3.swift
2025-08-22 18:11:30 +08:00

226 lines
7.8 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import SwiftUI
struct FilmAnimation1: View {
//
private let deviceWidth = UIScreen.main.bounds.width
private let deviceHeight = UIScreen.main.bounds.height
//
@State private var animationProgress: CGFloat = 0.0 // 0-1
@State private var isAnimating: Bool = false
@State private var animationComplete: Bool = false
//
private let reelImages: [[String]] = [
(0..<150).map { "film1-\($0+1)" }, //
(0..<180).map { "film2-\($0+1)" }, //
(0..<150).map { "film3-\($0+1)" } //
]
//
private let frameWidth: CGFloat = 90
private let frameHeight: CGFloat = 130
private let frameSpacing: CGFloat = 10
private let totalDistance: CGFloat = 2000 //
//
private let accelerationDuration: Double = 5.0 // 0-5s
private let constantSpeedDuration: Double = 6.0 // +5-11s
private var totalDuration: Double { accelerationDuration + constantSpeedDuration }
private var scaleStartProgress: CGFloat { accelerationDuration / totalDuration }
private let finalScale: CGFloat = 3.0 //
//
private let symmetricTiltAngle: Double = 8 //
private let verticalOffset: CGFloat = 140 //
private let initialMiddleY: CGFloat = 50 //
//
private let horizontalOffset: CGFloat = 30
var body: some View {
ZStack {
//
Color(red: 0.08, green: 0.08, blue: 0.08)
.edgesIgnoringSafeArea(.all)
//
FilmReelView3(images: reelImages[0])
.rotationEffect(Angle(degrees: -symmetricTiltAngle))
.offset(x: topReelPosition - horizontalOffset, y: -verticalOffset) //
.opacity(upperLowerOpacity)
.zIndex(1)
//
FilmReelView3(images: reelImages[2])
.rotationEffect(Angle(degrees: symmetricTiltAngle))
.offset(x: bottomReelPosition + horizontalOffset, y: verticalOffset) //
.opacity(upperLowerOpacity)
.zIndex(1)
//
FilmReelView3(images: reelImages[1])
.offset(x: middleReelPosition, y: middleYPosition)
.scaleEffect(currentScale)
.position(centerPosition)
.zIndex(2)
.edgesIgnoringSafeArea(.all)
}
.onAppear {
startAnimation()
}
}
// MARK: -
private func startAnimation() {
guard !isAnimating && !animationComplete else { return }
isAnimating = true
withAnimation(Animation.timingCurve(0.2, 0.0, 0.8, 1.0, duration: totalDuration)) {
animationProgress = 1.0
}
DispatchQueue.main.asyncAfter(deadline: .now() + totalDuration) {
isAnimating = false
animationComplete = true
}
}
// MARK: -
private var currentScale: CGFloat {
guard animationProgress >= scaleStartProgress else {
return 1.0
}
let scalePhaseProgress = (animationProgress - scaleStartProgress) / (1.0 - scaleStartProgress)
return 1.0 + (finalScale - 1.0) * scalePhaseProgress
}
// Y
private var middleYPosition: CGFloat {
if animationProgress < scaleStartProgress {
return initialMiddleY - (initialMiddleY * (animationProgress / scaleStartProgress))
} else {
return 0 // 5s
}
}
private var upperLowerOpacity: Double {
if animationProgress < scaleStartProgress {
return 0.8
} else {
let fadeProgress = (animationProgress - scaleStartProgress) / (1.0 - scaleStartProgress)
return 0.8 * (1.0 - fadeProgress)
}
}
private var centerPosition: CGPoint {
CGPoint(x: deviceWidth / 2, y: deviceHeight / 2)
}
// MARK: -
private var motionProgress: CGFloat {
if animationProgress < scaleStartProgress {
let t = animationProgress / scaleStartProgress
return t * t //
} else {
return 1.0 + (animationProgress - scaleStartProgress) *
(scaleStartProgress / (1.0 - scaleStartProgress))
}
}
//
private var topReelPosition: CGFloat {
totalDistance * 0.9 * motionProgress
}
//
private var middleReelPosition: CGFloat {
-totalDistance * 1.2 * motionProgress
}
//
private var bottomReelPosition: CGFloat {
totalDistance * 0.9 * motionProgress //
}
}
// MARK: -
struct FilmReelView3: View {
let images: [String]
var body: some View {
HStack(spacing: 10) {
ForEach(images.indices, id: \.self) { index in
FilmFrameView3(imageName: images[index])
}
}
}
}
struct FilmFrameView3: View {
let imageName: String
var body: some View {
ZStack {
//
RoundedRectangle(cornerRadius: 4)
.stroke(Color.gray, lineWidth: 2)
.background(Color(red: 0.15, green: 0.15, blue: 0.15))
//
Rectangle()
.fill(gradientColor)
.cornerRadius(2)
.padding(2)
//
Text(imageName)
.foregroundColor(.white)
.font(.caption2)
}
.frame(width: 90, height: 130)
//
.overlay(
HStack {
VStack(spacing: 6) {
ForEach(0..<6) { _ in
Circle()
.frame(width: 6, height: 6)
.foregroundColor(.gray)
}
}
Spacer()
VStack(spacing: 6) {
ForEach(0..<6) { _ in
Circle()
.frame(width: 6, height: 6)
.foregroundColor(.gray)
}
}
}
)
}
private var gradientColor: LinearGradient {
if imageName.hasPrefix("film1") {
return LinearGradient(gradient: Gradient(colors: [.blue, .indigo]), startPoint: .topLeading, endPoint: .bottomTrailing)
} else if imageName.hasPrefix("film2") {
return LinearGradient(gradient: Gradient(colors: [.yellow, .orange]), startPoint: .topLeading, endPoint: .bottomTrailing)
} else {
return LinearGradient(gradient: Gradient(colors: [.teal, .cyan]), startPoint: .topLeading, endPoint: .bottomTrailing)
}
}
}
//
struct FilmAnimation_Previews3: PreviewProvider {
static var previews: some View {
FilmAnimation1()
}
}