如何为macOS中NSBezierPath绘制的缺口路径实现手绘动画?
实现NSBezierPath缺口路径的手绘风格动画
要给你的缺口路径添加手绘风格动画,核心是模拟真实手绘的不规则线条、变速节奏、细微笔触变化这几个特点,以下是针对你现有代码的具体修改方案:
1. 生成带随机扰动的手绘路径
真实手绘的线条不会完全平滑,我们可以给路径的每个顶点和控制点添加微小的随机偏移,模拟手绘的抖动感:
首先添加一个生成随机偏移的工具方法:
private func randomOffset() -> CGFloat { // 控制偏移范围,数值越小抖动越细微 return CGFloat.random(in: -0.3...0.3) }
然后修改drawOutline里的路径生成逻辑,给每个点的坐标添加随机偏移:
private func drawOutline() { let size = self.frame.size let gap: CGFloat = 0.8 let notch = NSBezierPath() // 给每个点添加随机偏移 notch.move(to: NSPoint(x: 0.0 + randomOffset(), y: size.height + 1.0 + randomOffset())) notch.curve(to: NSPoint(x: notchRadius + randomOffset(), y: size.height - notchRadius + randomOffset()), controlPoint1: NSPoint(x: notchRadius - gap + randomOffset(), y: size.height + randomOffset()), controlPoint2: NSPoint(x: notchRadius + randomOffset(), y: size.height - gap + randomOffset())) notch.line(to: NSPoint(x: notchRadius + randomOffset(), y: notchRadius + randomOffset())) notch.curve(to: NSPoint(x: notchRadius * 2.0 + randomOffset(), y: 1.0 + randomOffset()), controlPoint1: NSPoint(x: notchRadius + randomOffset(), y: gap + randomOffset()), controlPoint2: NSPoint(x: notchRadius + gap + randomOffset(), y: 1.0 + randomOffset())) notch.line(to: NSPoint(x: size.width - 2 * notchRadius + randomOffset(), y: 1.0 + randomOffset())) notch.curve(to: NSPoint(x: size.width - notchRadius + randomOffset(), y: notchRadius + randomOffset()), controlPoint1: NSPoint(x: size.width - notchRadius - gap + randomOffset(), y: 1.0 + randomOffset()), controlPoint2: NSPoint(x: size.width - notchRadius + randomOffset(), y: gap + randomOffset())) notch.line(to: NSPoint(x: size.width - notchRadius + randomOffset(), y: size.height - notchRadius + randomOffset())) notch.curve(to: NSPoint(x: size.width + randomOffset(), y: size.height + 1.0 + randomOffset()), controlPoint1: NSPoint(x: size.width - notchRadius + randomOffset(), y: size.height - gap + randomOffset()), controlPoint2: NSPoint(x: size.width - notchRadius + gap + randomOffset(), y: size.height + randomOffset())) notch.close() // 原有描边逻辑保留 notch.lineWidth = 1.6 outlineColor.setStroke() notch.stroke() thePath = notch.cgPath // 更新或创建CAShapeLayer if shapeLayer == nil { shapeLayer = CAShapeLayer() shapeLayer.lineCap = .round // 设置圆角笔触,更贴近手绘笔锋 shapeLayer.lineJoin = .round shapeLayer.lineWidth = notch.lineWidth shapeLayer.strokeColor = NSColor.notchPurple.cgColor shapeLayer.fillColor = NSColor.clear.cgColor // 开启轻微外发光增强手绘质感 shapeLayer.shadowColor = NSColor.white.cgColor shapeLayer.shadowOffset = .zero shapeLayer.shadowRadius = 6.0 shapeLayer.shadowOpacity = 0.3 self.layer?.addSublayer(shapeLayer) } // 路径更新时同步shapeLayer的path shapeLayer.path = notch.cgPath animatePathDrawing() }
2. 替换线性动画为变速手绘节奏
原来的strokeEnd线性动画太规整,我们用**关键帧动画(CAKeyframeAnimation)**模拟手绘时的快慢停顿:
func animatePathDrawing() { self.alphaValue = 1.0 shapeLayer.strokeEnd = 0.0 // 1. 变速描边动画:模拟手绘的快慢节奏 let strokeAnimation = CAKeyframeAnimation(keyPath: "strokeEnd") // 控制描边进度的关键帧 strokeAnimation.values = [0.0, 0.12, 0.25, 0.4, 0.55, 0.7, 0.82, 0.9, 1.0] // 对应关键帧的时间点,不均匀分布模拟停顿和加速 strokeAnimation.keyTimes = [0.0, 0.1, 0.22, 0.38, 0.55, 0.7, 0.83, 0.9, 1.0] strokeAnimation.duration = 3.5 // 延长动画时间,更符合手绘速度 // 每个阶段用不同的缓动函数,增强自然感 strokeAnimation.timingFunctions = [ CAMediaTimingFunction(name: .easeInEaseOut), CAMediaTimingFunction(name: .easeIn), CAMediaTimingFunction(name: .easeOut), CAMediaTimingFunction(name: .easeInEaseOut), CAMediaTimingFunction(name: .easeIn), CAMediaTimingFunction(name: .easeOut), CAMediaTimingFunction(name: .easeInEaseOut), CAMediaTimingFunction(name: .easeIn) ] // 2. 笔触粗细变化动画:模拟手绘时的压力变化 let widthAnimation = CAKeyframeAnimation(keyPath: "lineWidth") widthAnimation.values = [1.2, 1.7, 1.4, 1.8, 1.5, 1.6] widthAnimation.keyTimes = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0] widthAnimation.duration = 3.5 // 配置动画循环和反转 strokeAnimation.repeatCount = .infinity strokeAnimation.autoreverses = true widthAnimation.repeatCount = .infinity widthAnimation.autoreverses = true // 添加动画到图层 shapeLayer.add(strokeAnimation, forKey: "strokeEndAnimation") shapeLayer.add(widthAnimation, forKey: "lineWidthAnimation") isAnimated = true }
3. 其他优化点
- 开启
shapeLayer的shadowOpacity(设置为0.2-0.4),让线条有轻微光晕,更贴近手绘的质感 - 设置
lineCap和lineJoin为.round,模拟手绘笔锋的圆角效果 - 调整随机偏移的范围(
randomOffset的区间),可以控制抖动的幅度,数值越小越细腻,越大越夸张
内容的提问来源于stack exchange,提问作者Avi Rok
相关产品推荐
相关产品推荐

