import SwiftUI // MARK: - 主视图:电影胶卷盲盒动效 struct FilmStripBlindBoxView: View { @State private var isAnimating = false @State private var revealCenter = false // 三格盲盒内容(使用 SF Symbols 模拟不同“隐藏款”) let boxContents = ["popcorn", "star", "music.note"] var body: some View { GeometryReader { geometry in let width = geometry.size.width ZStack { // 左边盲盒胶卷帧 BlindBoxFrame(symbol: boxContents[0]) .offset(x: isAnimating ? -width / 4 : -width) .opacity(isAnimating ? 1 : 0) // 中间盲盒胶卷帧(最终放大) BlindBoxFrame(symbol: boxContents[1]) .scaleEffect(revealCenter ? 1.6 : 1) .offset(x: isAnimating ? 0 : width) .opacity(isAnimating ? 1 : 0) // 右边盲盒胶卷帧 BlindBoxFrame(symbol: boxContents[2]) .offset(x: isAnimating ? width / 4 : width * 1.5) .opacity(isAnimating ? 1 : 0) } .onAppear { // 第一阶段:胶卷滑入 withAnimation(.easeOut(duration: 1.0)) { isAnimating = true } // 第二阶段:中间帧“开盒”放大 DispatchQueue.main.asyncAfter(deadline: .now() + 1.2) { withAnimation( .interpolatingSpring(stiffness: 80, damping: 12).delay(0.3) ) { revealCenter = true } } } } .frame(height: 140) .padding() .background(Color.black.opacity(0.05)) } } // MARK: - 盲盒胶卷帧:带孔 + 橙色背景 + SF Symbol struct BlindBoxFrame: View { let symbol: String var body: some View { ZStack { // 胶片边框(橙色 + 打孔) FilmBorder() // SF Symbol 作为“盲盒内容” Image(systemName: symbol) .resizable() .scaledToFit() .foregroundColor(.white.opacity(0.85)) .frame(width: 60, height: 60) } .frame(width: 120, height: 120) } } // MARK: - 胶片边框:#FFB645 背景 + 打孔 struct FilmBorder: View { var body: some View { Canvas { context, size in let w = size.width let h = size.height // 背景色:FFB645 let bgColor = Color(hex: 0xFFB645) context.fill(Path(CGRect(origin: .zero, size: size)), with: .color(bgColor)) // 打孔参数 let holeRadius: CGFloat = 3.5 let margin: CGFloat = 12 let holeYOffset: CGFloat = h * 0.25 // 左侧打孔(3个) for i in 0..<3 { let y = CGFloat(i + 1) * (h / 4) context.fill( Path(ellipseIn: CGRect( x: margin - holeRadius * 2, y: y - holeRadius, width: holeRadius * 2, height: holeRadius * 2 )), with: .color(.black) ) } // 右侧打孔(3个) for i in 0..<3 { let y = CGFloat(i + 1) * (h / 4) context.fill( Path(ellipseIn: CGRect( x: w - margin, y: y - holeRadius, width: holeRadius * 2, height: holeRadius * 2 )), with: .color(.black) ) } } } } // MARK: - Color 扩展:支持 HEX 颜色 extension Color { init(hex: UInt) { self.init( .sRGB, red: Double((hex >> 16) & 0xff) / 255, green: Double((hex >> 8) & 0xff) / 255, blue: Double(hex & 0xff) / 255, opacity: 1.0 ) } } // MARK: - 预览 struct FilmStripBlindBoxView_Previews: PreviewProvider { static var previews: some View { FilmStripBlindBoxView() .preferredColorScheme(.dark) } }