01 代码组件
使用代码构建滚动组件。
1.1 引入 Scroll module 和内容
通过以下代码从 framer 库中引入 Scroll 模组。
import {Scroll} from "framer"
引入画板中的滚动内容方式如下:先从画板中引入设计组件,需要先把画板创建为组件(create component),本例中为 Content,然后在代码中引用。
import { Content } from "./canvas"
使用时和其他 module 方法一致。
<Content/>
1.2 其他属性
import * as React from "react"
import { Frame, Scroll, useAnimation } from "framer"
import { Content } from "./canvas"
export function ScrollContainer() {
const controls = useAnimation()
controls.start({
y: 44,
transition: {
duration: 0.001,
},
})
return (
<Scroll
width={375}
height={812}
// 滚动内容的宽高
contentWidth={375}
contentHeight={1617}
// content 和 <Scroll/> 组件顶部的距离
// contentOffsetY={100}
// content 和 <Scroll/> 组件左侧的距离
// contentOffsetX={100}
// 是否可以拖拽
// dragEnabled={false}
// 禁止使用鼠标滚轮滚动
// wheelEnabled={false}
// 两个方向都能滚动
// direction="both"
// 只能在水平方向滚动
// direction="horizontal"
// 只能在垂直方向滚动(默认)
// direction="vertical"
// 滚动开始前的默认动画
animate={controls}
// 滚动时触发函数
onScroll={info => {
console.log(info.offset)
}}
// 滚动开始时触发
onScrollStart={() => {
console.log("scrollStarted")
}}
// 滚动结束时触发
onScrollEnd={() => {
console.log("scrollEnded")
}}
>
<Content />
</Scroll>
)
}
02 ScrollAnimate & useAnimation
ScrollAnimate 和 useAnimation 结合使得滚动组件滚动到某一特定位置。本例中我们让滚动组件默认向上移动 180 而非默认位置。

import * as React from "react"
import { Override, useAnimation } from "framer"
// 定义可以在所有函数中调用的变量 controls
let controls
export function scrollAnimate(): Override {
controls = useAnimation()
controls.start({
y: -180,
transition: {
duration: 0.00001,
},
})
return {
scrollAnimate: controls,
}
}
可通过判断 Data 中属性的值来控制滚动组件的动画,最终控制其位置。

import * as React from "react"
import { Override, useAnimation, Data } from "framer"
const appState = Data({
pageIndex: 0,
})
let controls
let controlsPage1
export function TrackScroll(): Override {
console.log("pageIndex", appState.pageIndex)
controls = useAnimation()
controlsPage1 = useAnimation()
return {
//===非常重要,为绝对等于(“1”===1 为 false),=为赋值,==为转换属性之后判断(“1”==1为 true),此例用==也可
scrollAnimate: appState.pageIndex === 1 ? controlsPage1 : controls,
}
}
export function button1(): Override {
return {
onTap() {
appState.pageIndex = 1
console.log("cc", appState.pageIndex)
controls.start({
y: -280,
})
},
}
}
export function button2(): Override {
return {
onTap() {
controls.start({
y: 0,
})
appState.pageIndex = 0
console.log(appState.pageIndex)
},
}
}
02 物体属性和滚动距离关联
使用 motionValue 和 useTransform 来绑定物体属性和滚动距离。

import * as React from "react"
import { Frame, Override, motionValue, useTransform } from "framer"
import { Content } from "./canvas"
const contentOffsetY = motionValue(0)
//将此 override 赋予自带 scroll 组件
export function TrackScroll(): Override {
return {
contentOffsetY: contentOffsetY,
}
}
//将此 override 赋予要改变属性的物体
export function card(): Override {
//滚动距离在[-100, 0, 100]区间变化时,物体缩放在 [0.5, 1, 1.5] 对应变动
const scale = useTransform(contentOffsetY, [-100, 0, 100], [0.5, 1, 1.5])
return {
scale: scale,
}
}

import * as React from "react"
import {
Frame,
Scroll,
useAnimation,
Override,
motionValue,
useTransform,
} from "framer"
import { Content } from "./canvas"
const contenOffsetY = motionValue(0)
export function trackScroll(): Override {
return {
contentOffsetY: contenOffsetY,
}
}
//3d变换卡片的父级 frame
export function parent(): Override {
return {
perspective: 150,
}
}
//3d 变换卡片
export function child(): Override {
const rotateX = useTransform(contenOffsetY, [-100, 0, 100], [-80, 0, 80])
return {
rotateX: rotateX,
originY: 1,
}
}
03 滚动达到阈值后改变 data 的值
当滚动距离到达设定阈值后,改变 Data() 方法设定的某一值,此值改变后,触发某一物体的动画。本例中当向上滚动距离大于 52 时,卡片开始透明度从 0 到 1 的动画。

import * as React from "react"
import { Override, Data, motionValue, useTransform } from "framer"
const data = Data({ isPastLimit: false })
const contentOffsetY = motionValue(0)
// 若向上滚动超过 52 ,则 data.isPastLimit 为 true,反之为 false
contentOffsetY.onChange(offset => (data.isPastLimit = offset < -52))
export function TrackScroll(): Override {
return { contentOffsetY: contentOffsetY }
}
export function ShowCardIfPastLimit(): Override {
return {
opacity: 0, // set it to 0 initially
animate: data.isPastLimit ? { opacity: 1 } : { opacity: 0 },
}
}
04 视差滚动
滚动层在相同的时间向上滚动 100,是差层只对应向上滚动 50。

import * as React from "react"
import { Override, motionValue, useTransform } from "framer"
const contentOffsetY = motionValue(0)
export function TrackScroll(): Override {
return {
contentOffsetY: contentOffsetY,
}
}
export function ParallaxLayer(): Override {
// 向上滚动 100,卡片向上移动 50
const top = useTransform(contentOffsetY, [0, -100], [125, 75], {
clamp: false,
})
return {
top: top,
}
}
05 动态appBar
如 ios 设置页面的 Appbar 效果。

import * as React from "react"
import { Override, motionValue, useTransform } from "framer"
const contentOffsetY = motionValue(0)
export function TrackScroll(): Override {
return {
contentOffsetY: contentOffsetY,
}
}
export function appBar(): Override {
const opacity = useTransform(contentOffsetY, [-40, -52], [0, 1], {
clamp: false,
})
return {
opacity: opacity,
}
}
06 返回顶部 滚动达到阈值触发动画
controls需要在最外面定义,以便可以跨 function 引用。

import * as React from "react"
import { Override, useAnimation } from "framer"
// 定义可以在所有函数中调用的变量 controls
let controls
export function TrackScroll(): Override {
controls = useAnimation()
return {
scrollAnimate: controls,
}
}
export function BackToTop(): Override {
return {
onTap() {
controls.start({
y: 0,
transition: {
type: "spring",
damping: 15,
},
})
},
}
}
07 Sticky Header 悬浮

import * as React from "react"
import { Override, motionValue, useTransform } from "framer"
const contentOffsetY = motionValue(0)
export function TrackScroll(): Override {
return {
contentOffsetY: contentOffsetY,
}
}
export function StickyHeader(): Override {
// [75,0]对应[87,87]为了下拉时蓝色区域不跟着往下移动,[-75,-150]对应[12,12]为了进度条置顶后不再向上滑动
const top = useTransform(
contentOffsetY,
[75, 0, -75, -150],
[87, 87, 12, 12],
{
clamp: false,
}
)
return {
top: top,
}
}

