Party Animals 是一个基于物理引擎的多人派对游戏,我在看到它的官网的时候发现一些还不错的实现方式,于是决定参照其中的角色选择功能,实现一个类似的效果。
效果如视频 Motion Effect Demo.mp4 所示,需要满足:
- 视觉效果应该和视频中展现的一致,包括缩放、缓入缓出以及回弹等动画细节
- 交互细节也应该和视频中一致,包括鼠标悬浮、点击、选中以及按压等效果
- 可以使用鼠标左右拖动列表,并且不借助于滚动条
- 可以使用左右按键操作选中的角色,并且将选中的角色居中
所有素材资源均可在官网上获取。
查看 在线演示。
我们参考 React 官方指导手册 Thinking in React 来进行开发。
构建静态的组件
参考视频中的样子,我们可以搭建起静态的框架。定义一个 RolePicker
组件,里面包含了一个由 RoleAvatar
组成的列表。
RoleAvatar
组件有名为 isActive
的 props,会影响组件的样式,同时该组件也有自己的样式,针对需要应用多个样式的情况,可以使用 classNames
库来合并多个 className
。
观察视频可以发现,RoleAvatar
组件被 hover
的时候,存在一个两段的动画
- 鼠标一进入,背景快速的变大
- 鼠标长时间悬浮,背景呈现呼吸效果
我们可以使用 animation
来实现这样的效果,它本身也支持将值设置为多个动画,并逐个执行。因为悬浮效果为变大,所以我们通过 @keyframes
定义了一个变大的效果,然后两段动画其实结果一样,只是变化的过程不一样,变化过程我们使用贝塞尔曲线来加以区别。最终的效果如下:
经过以上的步骤,我们基本完成了静态组件的开发,接下来我们来处理一些用户的输入。
实现拖动效果
若要实现鼠标拖动的效果,我们可以搭配使用 onmousedown
,onmousemove
以及 onmouseup
三个事件来实现。
使用标志 dragFlag
来判断当前是否在按住并拖动鼠标,只有按下的时候鼠标移动才被认为是在拖动。同时在鼠标抬起之后需要将标志清除掉。
这样就能在按下并拖动鼠标的时候获取到鼠标的移动量,transfrom 的 translateX()
属性可以移动元素,我们可以通过将鼠标移动量赋值给 translateX()
来实现拖动效果。
同时需要注意一些边界条件,不能将列表拖到容器外。计算容器的宽度以及列表的实际宽度(即滚动宽度 scrollWidth
),左右两侧需要分别计算。
现在我们已经可以正常使用鼠标拖动我们的列表了,但是别忘了添加移动端的支持,我们将触摸滑动的事件对应的也进行一下处理,事件类型有些许区别,在获取指针位置的时候需要注意一下。
最后再添加一个 onClick
事件用来处理角色选中。
使用左右方向按键控制选中角色
这一步我们需要添加方向按键的支持,可以通过左右方向按键切换选中的角色,并将角色自动居中。我们首先监听一下键盘事件,按左方向键选中上一个角色,按右方向键选中下一个角色。
之前使用鼠标拖动,我们只需要将拖动量转换为位移就可以了,并不需要进行太多的计算,而如果要实现居中的效果,我们需要进行一些计算。
分别计算以当前选中角色的中心为切分点的左边的宽度 leftWidth
以及右边的宽度 rightWidth
, 并将这个结果与容器的一半宽度 halfConWidth
做比较,如果 leftWidth > halfConWidth
则将位移量设置为差值,反之则不移动,因为已经到了列表的边界了。右侧 rightWidth
同理进行计算。
同样的,通过 translateX()
让列表动起来。
现在我们可以使用左右方向按键切换选中的角色了。还有一个小细节,使用该方法进行居中的时候有一个缓入缓出的效果,即需要给 translateX()
添加一个 ease-in-out
的的过渡效果,但是需要注意的是拖动的时候不能有这个属性,不然拖不动,需要搭配拖动标志 dragFlag
来动态添加 ease-in-out
。
在两侧添加箭头按钮
键盘操作比较隐蔽,我们直接在列表的两侧添加两个箭头,实现与左右方向按键同样的功能。
现在可以直接在屏幕上点击也可以切换选中的角色并将其居中显示。
鼠标点击事件与拖动同时触发
使用中我们发现,我们使用鼠标进行拖动的时候,拖动结束鼠标松开,会触发 RoleAvatar
的 onClick
事件,导致那个角色被选中,但是我们并不想在拖动结束后选中某一个角色,所以在拖动的时候我们需要让 onClick
事件失效,可以通过一个简单的计时来判断,计算鼠标按下到松开的时间,如果超过 100ms,则认为不是点击事件。
在 onClick
事件触发的时候,计算一下时长。
通过以上的步骤,我们就完成了一个还算看得过去的角色选择器,当然也还有很多可以优化的空间,比如 css 动画不够细致、角色选中以及未被选中两个状态切换的时候缺少回弹效果等,后面有机会可以再优化优化。