云村来了,各位客官这么看?
战略
核心
热评墙
mlog
类似于Vlog一样,前者是用视频记录自己的生活。
后者是用音乐记录自己的生活
project001
视频展示:
源文件:
交互动效分析
搜索栏:
一开始的话,搜索栏是不出现的。随着用户向上滑动,当往下滑动的时候,搜索栏才会出现。
我估计可能云村这里,交互设计师是想让用户先浏览大数据推荐的或者是优选的内容,一般当用户向下滑动的时候,有可能会搜索内容,所以才这样设计的。
发图文和视频的小按钮
这里的按钮,处在大拇指扫射的范围内,正好是向左的极限距离(右手单手操作的情况下)。
我估计啊,应该是右手操作比左手操作的情况多,所以放在左下的位置有两个。
- 方便右手操作,按钮的位置处在右手大拇指可触及的范围内
- 放在左变,是怕右手误触。
- 一开始按钮会有一个微动效,这可能也是官方为了鼓励用户多发发自己的动态。
内容区
内容区我大概看了一下,主要有三类。
- 视频模块
- 图文模块
- 音频图文模块
project002
今日份更新:发文小按钮动效
demo源文件:
总文件源文件:
案例分析:
这个小动效是首次跳转到云村这个导航时,会触发一次。
当点击返回的时候,会触发一次。
意在引导用户发文或者发视频。
潜在思考
为什么他这里不可以设置为自动能拖动小按钮的位置呢?
克制 与 自由 ?
技巧分析
主要用了 useAnimation ,组件间交互比data更为灵活,尤其是在处理复杂的动画的时候。
代码展示:
import { Override, useAnimation } from "framer"
export function Tap(): Override {
return {
onTap() {
ButtonCircleControls.start({
rotate: 360,
transition: {
duration: 1,
loop: 1,
repeatDelay: 1.5,
},
})
ButtonPlusControls.start({
scale: 0,
opacity: 0,
transition: {
yoyo: 1,
duration: 1,
repeatDelay: 1.5,
ease:"easeInOut"
},
})
ButtonMControls.start({
scale: 1,
opacity: 1,
transition: {
delay:0.3,
yoyo: 1,
duration: 1,
repeatDelay: 1,
ease:"easeInOut"
},
})
},
}
}
//buttonCircle
let ButtonCircleControls
export function ButtonCircle(): Override {
ButtonCircleControls = useAnimation()
return {
animate: ButtonCircleControls,
}
}
//button+
let ButtonPlusControls
export function ButtonPlus(): Override {
ButtonPlusControls = useAnimation()
return {
animate: ButtonPlusControls,
}
}
//buttonM
let ButtonMControls
export function ButtonM(): Override {
ButtonMControls = useAnimation()
return {
scale: 0,
animate: ButtonMControls,
}
}
project003
本次更新:表白墙滚动效果
demo源文件
案例分析
这个热评墙的热评滚动效果是用户首次点到云村这里的时候,会触发的。
一开始是一个人性化的打动你的文案。然后接下来是三个热评的文案,再然后就还是那个文案。
关于这个热评墙,既然是网易云做的,那应该就和音频有关系。果不其然,你打开之后,就会自动播放音乐,返回后音乐自动关闭,就像是一个音乐的热评墙,看文字的时候带着音乐。
技巧分析
这次用了 scroll 和 page 两种效果,page的滚动的时候,没有动画过渡,也不能添加。 scroll就可以添加过渡。
代码展示
import { Override, Data, useAnimation } from "framer"
const data = Data({
number: 0,
y: 0,
})
export function Tap(): Override {
return {
onTap() {
;(data.number += 1), (data.y -= 14)
controls.start({
y: data.y,
transition: {
type: "spring",
damping: 13,
stiffness: 110,
},
})
},
}
}
export function Page(): Override {
return {
currentPage: data.number,
}
}
let controls
export function Scroll(): Override {
controls = useAnimation()
return {
scrollAnimate: controls,
}
}
project004
本次更新:表白墙内容页交互动效分析
交互分析
页数展示
number / 31 总共是31个热评
头像和关注提示
头像-名字 —-> 头像-关注 —-> 头像-已关注 —->. 头像-名字
这个啊 其实是比较特别的。一般情况下,我们看到关注会在名字右边,点击关注后,会一直显示已关注。
云音乐这波把已关注之后,取消显示,是为了什么呢?增加了取消关注的成本,
用户需要点进已关注的那个人的个人主页才能取消。
热评的文字
我们翻页的过程中发现,每个热评的字体大小是不一样的,但是letter-spaciing 和 line-height 是一样的,所以仅仅是字号 fontSize 不一样。经过我对原UI稿的一顿分析,得出以下这么几个结论。
- 当 字数< 44的时候,用 27pt 的字号
- 当 字数>44 且 字数<65的时候,用 23pt 的字号
当字数>65 且 字数 <152的时候,用 16pt 的字体
!!最大的字数限制为152。这个系统筛选热评的时候,是有限制的应该。
这个其实也是我个人没怎么见过的,根据字数来控制字体的大小。
line-height 关于行高的文章,可以看一下
头像关注动效
视频展示
demo源文件
技巧分析
本来想用data去做的,发现有复杂的连续运动。于是尝试着用 useAnimation 来做,然后一直报错,总有哪里不对。接下来,又看了一下官网的API。—-async—函数。这个就比较优秀了,类似于时间轴动画。建议哈,如果有时间轴动画,就可以用async函数,配合useAnimation就可以做一些复杂的时间轴动画了。
源代码
import * as React from "react"
import { Override, useAnimation } from "framer"
//autotap
export function Tap(): Override {
async function sequence() {
await nameControls.start({
opacity: 0,
})
await bgControls.start({
width: 88,
transition: {
duration: 0.25,
},
})
await concernControls.start({
opacity: 1,
transition: {
duration: 0.2,
},
})
}
return {
onTap() {
sequence()
},
}
}
//name
let nameControls
export function Name(): Override {
nameControls = useAnimation()
async function sequence() {
await concernControls.start({
opacity: 0,
transition: {
duration: 0.2,
},
})
await bgControls.start({
width: 102,
transition: {
duration: 0.1,
},
})
await concernedControls.start({
opacity: 1,
transition: {
duration: 0.2,
},
})
await concernedControls.start({
opacity: 0,
transition: {
duration: 0.2,
delay: 3,
},
})
await bgControls.start({
width: 32,
transition: {
duration: 0.25,
},
})
await nameControls.start({
opacity: 1,
})
}
return {
animate: nameControls,
onTap() {
sequence()
},
}
}
//bg
let bgControls
export function Bg(): Override {
bgControls = useAnimation()
return {
animate: bgControls,
}
}
//concern
let concernControls
export function Concern(): Override {
concernControls = useAnimation()
return {
animate: concernControls,
}
}
//concenrned
let concernedControls
export function Concerned(): Override {
concernedControls = useAnimation()
return {
animate: concernedControls,
}
}
project005
本次更新:根据热评的文字的数量决定文字的大小
视频展示
demo源文件
技巧分析
经过对UI稿件的分析,热评文字的大小大概有:17pt , 21pt , 24pt , 27pt
24 和 27 的操作搞不懂啊,同样字体数量的情况下,24 和 27 都有。我又看了云村的机制,热评墙的内容是从瀑布流的内容中弄出来的。 后台工程师,应该也是根据字体的数量判断的,其实我这种不是很严谨,英文和中文其实字符占的距离是不一样的,暂时先实现这个效果吧。
framer X,因为这次要根据字体数量作为判断,那用Override就不行了,就得用原生的code组件了,所以这次的代码和以前的看着也不一样。主要是用了Stack的原理,使得字体的高度变化的同时,下面的模块也能跟上去。
源代码
import * as React from "react"
import { Frame, addPropertyControls, ControlType, Stack } from "framer"
import { Music } from "./canvas"
// Open Preview: Command + P
// Learn more: https://framer.com/api
export function Font(props) {
console.log(props.text.length)
const textLength = props.text.length
if (textLength < 44) {
return (
<Stack
direction="vertical"
gap={12}
alignment="start"
distribution="start"
>
<p
style={{
width: 329,
fontSize: 27,
color: "white",
textAlign: "left",
letterSpacing: 1,
lineHeight: 1.65,
fontWeight: 700,
//@ts-ignore
wordBreak: "break-Word",
}}
>
{props.text}
</p>
<Music />
</Stack>
)
} else if (textLength >= 44 && textLength < 65) {
return (
<Stack
direction="vertical"
gap={12}
alignment="start"
distribution="start"
>
<p
style={{
width: 329,
fontSize: 21,
color: "white",
textAlign: "left",
letterSpacing: 1,
lineHeight: 1.65,
fontWeight: 700,
//@ts-ignore
wordBreak: "break-Word",
}}
>
{props.text}
</p>
<Music />
</Stack>
)
} else {
return (
<Stack
direction="vertical"
gap={12}
alignment="start"
distribution="start"
>
<p
style={{
width: 329,
fontSize: 17,
color: "white",
textAlign: "left",
letterSpacing: 1,
lineHeight: 1.65,
fontWeight: 700,
//@ts-ignore
wordBreak: "break-Word",
}}
>
{props.text}
</p>
<Music />
</Stack>
)
}
}
Font.defaultProps = {
text: "Get started!",
}
// Learn more: https://framer.com/api/property-controls/
addPropertyControls(Font, {
text: {
title: "Text",
type: ControlType.String,
defaultValue: "Hello Framer!",
},
})
project006
本次更新:爱心关注动效
视频展示
demo源文件
技巧分析
这里说一下思考过程。
一开始我是想弄一个code组件,通过控制property controls的的内容来控制。后来发现,爱心的图形不行放大和缩小不行。因为Graphic的大小是不能通过代码控制的。
后来,我就想,也可以通过SVG code来实现啊。但是,发现同样一个问题,code组件中的SVG code,不能通过代码更改其大小。
再后来想了一下,也可能实际生产的过程中,程序员老哥可能是用2张png图片呢。到此,焕然大悟,所以用图片解决了问题。
Framer X知识点:useAnimation ,Data
通过onTap来控制事件的布尔类型,然后调用两个函数,一个是系列关注时会发生的动画,一个是取消时会发生的动画。
源代码
import {Override , Data , useAnimation} from 'framer'
//data
const data = Data({
open:false
})
//tap
export function Tap():Override {
//true
async function trueSequence() {
//border opacity
await borderedControls.start({
opacity:0,
transition:{
duration:.1
}
})
//filled oapcity
await filledControls.start({
opacity:1,
transition:{
duration:.1
}
})
//filled scale
await filledControls.start({
scale: 1.3,
transition: {
duration: 0.05
}
})
await filledControls.start({
scale: 1,
transition: {
duration: 0.05
}
})
//concerned
await concernedControls.start({
opacity:1,
transition:{
duration:0.25,
delay:0.15
}
})
await concernedControls.start({
opacity:0,
transition:{
duration:0.1,
delay:1
}
})
//filled
await filledControls.start({
opacity: 0.8,
transition: {
duration: 0.05
}
})
}
//false
async function falseSequnce() {
//filled opacity
await filledControls.start({
opacity: 0,
transition: {
duration: .1,
delay:0.35
}
})
//bordered opacity
await borderedControls.start({
opacity: 1,
transition: {
duration: .1
}
})
//cancle
//concerned
await cancleControls.start({
opacity: 1,
transition: {
duration: 0.25
}
})
await cancleControls.start({
opacity: 0,
transition: {
duration: 0.1,
delay: 1
}
})
}
return{
onTap(){
data.open = !data.open
if(data.open == true){
trueSequence()
}else{
falseSequnce()
}
}
}
}
//bordered
let borderedControls
export function bordered():Override {
borderedControls = useAnimation()
return{
animate: borderedControls
}
}
//filled
let filledControls
export function filled():Override {
filledControls = useAnimation()
return{
animate: filledControls
}
}
//concerned
let concernedControls
export function concerned():Override {
concernedControls = useAnimation()
return{
animate: concernedControls
}
}
//cancle
let cancleControls
export function cancle():Override {
cancleControls = useAnimation()
return{
animate: cancleControls
}
}