这是一个为 Python 对象提供 CSS 风格过渡(transition)功能的轻量级库。你可以定义多个状态(state),每个状态为对象的属性指定目标值和过渡配置( 持续时间、延迟、缓动函数),然后通过 TransitionGroup 在状态之间切换,实现属性的平滑过渡。其行为模仿了 Web 前端中 CSS Transition 的特性,包括反转缩短(reverse shortening)
目前库以源码形式提供,你可以将文件放置在你的项目中直接导入:
from INS-Transition import TransitionGroup, State, TransitionData需要依赖 Python 3.6+。
TransitionData 定义了过渡的持续时间、缓动函数和延迟。类似于 CSS 的 transition-duration、transition-timing-function、transition-delay 。
data = TransitionData(
transition_duration='0.5s', # 支持字符串如 '0.5s' 或数字(秒)
transition_timing_function=ease, # 缓动函数
transition_delay='0.1s' # 延迟时间
)transition_duration以秒或毫秒为单位指定过渡动画所需的时间。默认值为'0s',表示不出现过渡动画。特别地,负数时间也会被存储为'0s'。
其支持以下形式的参数:- CSS 风格的字符串,如
'100ms','.5s' - 一个整数或浮点数,以秒为单位
- CSS 风格的字符串,如
transition_timing_function设置如何计算受过渡效果影响的属性的中间值。 其支持以下形式的参数:- 代码形式,需要通过
from INS-Transition import 需要的函数导入,具体包含:- 预制的缓动函数
linear,ease,ease_in,ease_in_out,ease_out - 自定义缓动函数,如
CubicBezier(0.42, 0.0, 0.58, 1.0)
- 预制的缓动函数
- CSS 风格字符串形式,具体包含:
- 预制的缓动函数
'linear','ease','ease-in','ease-in-out','ease-out' - 自定义缓动函数,如
'cubic-bezier(.42, 0, .58, 1)'
- 预制的缓动函数
- 代码形式,需要通过
transition_delay规定了在过渡效果开始作用之前需要等待的时间。值的格式与transition_duration相同,表明动画过渡效果将在何时开始。取值为正时会延迟一段时间来响应过渡效果;取值为负时与 CSS 处理方式相同,这里不做介绍。
Property 内部管理对象某个属性的过渡逻辑。通常你不需要直接创建它,而是通过 TransitionGroup 自动生成。
每个 Property 持有:
object_class: 需要进行过渡操作的外部对象name: 属性的名称value:当前值(直接读写对象属性,通过getattr(object_class, name)以及setattr)after_change_value:最终目标值transition_data:过渡配置transition:当前正在进行的过渡实例(内部)
State 是一组属性目标值和过渡配置的集合。你可以通过 State.create() 快速创建。
对于状态中的每个属性,创建的格式如下:
属性名 = (目标值, TransitionData对象)
state_a = State.create(
x=(100, TransitionData('1s')),
y=(200, TransitionData('0.5s', linear))
)状态之间可以继承:子状态若未定义某属性,会自动从父状态(通常是默认状态)获取。
每个 State 类内部不会存储状态的名称,状态的名称将在 TransitionGroup 中统一管理
TransitionGroup 是核心类,它持有一个对象和多个状态,负责在切换状态时更新所有属性的过渡。
你需要在每一帧调用 update() 方法来实时计算所有属性的过渡
切换状态需要使用 set_state(字符串形式的状态名称) 方法
group = TransitionGroup(
object_class=box, # 要控制的对象
default_state=default_state, # 默认状态,其状态名称将被赋予为 'default'
lerp_funcs={'x': my_lerp}, # 可选,为特定属性指定插值函数
state1=state1, # 其他状态,格式为 状态名称 = State对象
state2=state2
)对于非数值类的属性,lerp_funcs 的指定是必要的。lerp_funcs 是一个字典,表示每个属性对应的插值函数(一般应当为线性插值)
自定义的插值函数要求符合以下格式:
def lerp(t, value1, value2):
return new_value其中,t 是表示进度的浮点数,决定了结果处于 value1 与 value2 之间的位置。特别地,0表示返回 value1,1表示返回 value2。
比如,这是内置的插值函数:
def lerp(t: float, value1, value2):
"""线性插值函数"""
return value1 + (value2 - value1) * t另给出一个示例,这是用于插值 pygame.Color 的函数:
def lerp_color(t: float, color1: pygame.Color, color2: pygame.Color):
"""颜色线性插值函数"""
return color1.lerp(color2, t)Transition 是内部类,表示一个正在进行的过渡过程。用户一般无需直接操作它。
- 持续时间、延迟 支持字符串(如
'0.3s')或数字(秒)。 - 缓动函数:内置贝塞尔曲线缓动函数
- 多个属性可独立配置:每个属性可以在不同状态中拥有自己的过渡参数。(若未配置,将自动从
'default'状态继承)
这是 CSS 过渡的一个细节:当一个过渡正在进行时,如果目标值被改回过渡开始时的值,过渡不会重新开始,而是“反转”并缩短剩余时间。本库实现了这一行为。这一特性使得状态切换更加自然。
初始化参数:
transition_duration(str/float):持续时间,默认'0s'。transition_timing_function(str/callable):缓动函数,默认ease。transition_delay(str/float):延迟,默认'0s'。
属性:
duration(float):持续时间(秒)。timing_function(callable):缓动函数。delay(float):延迟(秒)。combine_duration(float):delay + duration。
通常不直接实例化,由 TransitionGroup 自动创建。
方法:
update():更新属性值(基于当前过渡状态)。应在循环中调用。value(property):获取/设置当前值(直接读写对象属性)。
类方法:
create(**kwargs):快速创建状态。参数格式为属性名=(目标值, TransitionData对象)。none_transition_data:类属性,表示无过渡的默认配置。
实例方法:
add_property(property_name, target_value, transition_data=None):添加或更新属性。inherit_state(father_state):从父状态继承未定义的属性。
初始化参数:
object_class:要控制的对象。default_state(State):默认状态。lerp_funcs(dict, optional):为特定属性指定插值函数,格式{属性名: 函数}。**kwargs:额外的状态,如state1=state_obj。
方法:
add_state(name, state):添加一个命名状态。add_states(**kwargs):批量添加状态。update():计算所有属性的过渡。set_state(new_state_name):切换到指定状态,更新每个属性的目标值和过渡配置。
属性:
current_state_name:字符串,当前状态名。property:字典,{属性名: Property对象}。states:字典,{状态名: State对象}。
按照以下四个步骤,你可以快速上手使用 INS-Transition 实现属性平滑过渡:
-
选定需要应用过渡的属性 确定你想要实现动画效果的对象属性,例如位置(
x,y)、尺寸(width,height)、颜色(color)等。这些属性将作为过渡的目标。 -
设定
'default'状态 创建一个State对象作为默认状态,为每个属性指定初始值。default = State.create( x=(0, TransitionData('1s')), y=(0, TransitionData('1s')) )
这类似于以下 CSS 代码:
.box { transform: translate(0px, 0px); transition: transform 1s ease; }
-
设定其他状态 根据需要创建其他状态(如
'hover'、'active')hover = State.create( x=(100, TransitionData('0.5s', ease_out)), y=(200, TransitionData('0.5s', ease_out)) )
这类似于以下 CSS 代码:
.box:hover { transform: translate(100px, 200px); transition: transform 0.5s ease-out; }
-
主循环中执行
update()方法 将对象和状态绑定到TransitionGroup,并在每一帧(如游戏循环)中调用其update()方法。切换状态时使用set_state(),update()会自动计算并更新对象的属性值。group = TransitionGroup(my_object, default, hover=hover) while running: # 主循环 group.update() # 每帧调用,更新属性 # 渲染对象... if 条件满足: group.set_state('hover') # 切换到 hover 状态
下面是一个更完整的示例,你也可以查看 slider_test.py 这个更复杂的示例
import pygame
import sys
from INS_Transition import State, TransitionGroup, TransitionData
pygame.init()
w, h = 800, 600
screen = pygame.display.set_mode((w, h), pygame.RESIZABLE)
clock = pygame.time.Clock()
class Button:
def __init__(self):
self.center = (400, 300)
self.w, self.h = 200, 100
self.state = False
default_state = State.create(
w = (self.w, TransitionData('1s')),
h = (self.h, TransitionData('1s'))
)
focus_state = State.create(
w = (self.w * 1.5, TransitionData('1s')),
h = (self.h * 1.5, TransitionData('1s'))
)
self.transition_group = TransitionGroup(self, default_state, focus = focus_state)
def get_rect(self):
rect = pygame.Rect(0, 0, round(self.w), round(self.h))
rect.center = self.center
return rect
def render(self, sf):
pygame.draw.rect(sf, (255, 0, 0), self.get_rect(), border_radius=20)
def handle_event(self, _event):
if _event.type == pygame.MOUSEBUTTONDOWN and self.get_rect().collidepoint(_event.pos):
self.state = not self.state
self.transition_group.set_state('focus' if self.state else 'default')
def update(self):
self.transition_group.update()
button = Button()
while 1:
pygame_events = pygame.event.get()
for event in pygame_events:
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
button.handle_event(event)
screen.fill('white')
button.update()
button.render(screen)
pygame.display.flip()
clock.tick(120)