详情页关注动效
视频展示:
源文件:
头条动效.framerx.zip
代码展示:
关注动效部分Override
import { Override, Data } from "framer"const data = Data({ isGetApi: false, isOpen: false, isListOpen: false,})//关注export function concern(): Override { return { onTap() { ;(data.isOpen = true), setTimeout(() => { ;(data.isGetApi = true), (data.isListOpen = true) }, 2200) }, visible: data.isGetApi ? false : true, animate: data.isGetApi ? { opacity: 0, } : { opacity: 1, }, transition: { duration: 0.1, }, }}//关注文字export function concernFont(): Override { return { animate: data.isOpen ? { opacity: 0 } : { opacity: 1 }, }}//关注请求API-circleexport function getApiCirlce(): Override { return { animate: data.isOpen ? { opacity: 1 } : { opacity: 0 }, }}//已关注export function concerned(): Override { return { onTap() { setTimeout(() => { ;(data.isGetApi = false), (data.isListOpen = false) }, 2200) data.isOpen = false }, animate: data.isGetApi ? { opacity: 1, x: -38, } : { opacity: 0, x: 0, }, transition: { duration: 0.35, }, }}//已关注关注文字export function concernedFont(): Override { return { animate: data.isOpen ? { opacity: 1 } : { opacity: 0 }, }}//已关注请求API-circleexport function getApiCirlced(): Override { return { animate: data.isOpen ? { opacity: 0 } : { opacity: 1 }, }}//下拉箭头export function arrowGroup(): Override { return { onTap() { data.isListOpen = !data.isListOpen }, animate: data.isGetApi ? { opacity: 1, } : { opacity: 0, }, }}//箭头方向export function arrow(): Override { return { rotate: data.isListOpen ? 0 : 180, }}//内容export function content(): Override { const variants = { open: { y: 200, transition: { duration: 0.35, }, }, closed: { y: 0, transition: { duration: 0.25, }, }, } return { initial: "closed", animate: data.isListOpen ? "open" : "closed", variants: variants, }}
滚动显示动效 override
import { Override, motionValue, Data } from "framer"//dataconst data = Data({ isScrollToUser: false,})const scrollY = motionValue(0)scrollY.onChange(point => { console.log(point) if (point < -120) { data.isScrollToUser = true } else { data.isScrollToUser = false }})export function Scroll(): Override { return { contentOffsetY: scrollY, }}export function userInfo(): Override { return { animate: data.isScrollToUser ? { opacity: 1 } : { opacity: 0 }, transition:{ duration:0.15 } }}export function Title(): Override { return { animate: data.isScrollToUser ? { opacity: 0 } : { opacity: 1 }, transition: { duration: 0.15 } }}
真实数据API调用部分 code
import * as React from "react"
import { Frame, addPropertyControls, ControlType, Stack } from "framer"
import { Close } from "./canvas"
import styled from "styled-components"
import { useState, useEffect } from "react"
//style
const Content = styled.div`
width: 128px;
height: 162px;
overflow: visible;
background-color: #ffffff;
border-radius: 6px;
padding-top:8px;
padding-left:9px;
padding-right:9px
`
const Photo = styled.div`
width: 60px;
height: 60px;
overflow: visible;
background-image:url(${props => props.background});
background-size:100% 100%;
border-radius: 30px;
margin:0 auto;
`
const Name = styled.p`
width: 110px;
height: 16px;
font-family: "PingFangSC-Regular", "PingFang SC", serif;
color: #000000;
font-size: 14px;
letter-spacing: 0px;
line-height: 1.2;
font-weight: 590;
text-align: center;
margin:0 auto;
margin-top:6px;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
`
const Introduce = styled.p`
width: 90px;
height: 30px;
font-family: "PingFangSC-Regular", "PingFang SC", serif;
color:"#333333";
font-size: 11px;
letter-spacing: 0px;
line-height: 1.4;
font-weight: 400;
text-align: center;
margin:0 auto;
margin-top:3px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
`
const Concern = styled.div`
color:white;
width: 102px;
height: 24px;
overflow: visible;
background-color:#f85959;
border-radius: 4px;
font-family: "PingFangSC-Regular", "PingFang SC", serif;
font-size: 12px;
letter-spacing: 0px;
line-height: 24px;
font-weight: 400;
text-align:center;
margin:0 auto;
margin-top:6px;
`
export function CareList(props) {
const { name, introduce, photo } = props
// 从API中获取JSON
const [data, setData] = useState(null)
useEffect(() => {
fetch("http://mock-api.com/0ZzR0mne.mock/care")
.then(response => response.json())
.then(data => {
if (data.list) {
setData(data.list)
}
})
}, [])
//如果数据还没有返回值,则返回loading
if (!data) return <Frame>Loading...</Frame>
const { list = [] } = data
const careLists = data.map((list, index) => {
return (
<Content key={index}>
<Photo background={list.photo} />
<Close right={4} top={4} />
<Name> {list.name} </Name>
<Introduce> {list.introduce} </Introduce>
<Concern> 关注 </Concern>
</Content>
)
})
return (
<Stack
gap={8}
distribution="start"
direction="horizontal"
height={props.height}
paddingLeft={13}
>
{careLists}
</Stack>
)
}
CareList.defaultProps = {
name: "中国电力报是个大啦啊",
introduce: "全国电力行业唯一的官方报纸行业的龙头老大",
photo:
"https://cdn.dribbble.com/assets/global_design_survey_2019/header/jeroen-van-eerden-be3330933fbe57f9edcce62654c5a2c74e24a2e9a75a8c62be73f71df6bbd372.png",
}
// Learn more: https://framer.com/api/property-controls/
addPropertyControls(CareList, {
photo: {
title: "photo",
type: ControlType.String,
defaultValue:
"https://cdn.dribbble.com/assets/global_design_survey_2019/header/jeroen-van-eerden-be3330933fbe57f9edcce62654c5a2c74e24a2e9a75a8c62be73f71df6bbd372.png",
},
name: {
title: "name",
type: ControlType.String,
defaultValue: "中国电力报",
},
introduce: {
title: "introduce",
type: ControlType.String,
defaultValue: "全国电力行业唯一的官方报纸行业的龙头老大",
},
})
悬浮播放按钮
视频讲解
源文件
头条动效_悬浮按钮.framerx.zip
代码展示:
按钮悬浮部分Override
import { Override, Data, motionValue, useAnimation } from "framer"
const data = Data({
isPlay: false,
length: 0,
isControlsListOpen: false,
isRelease: 0,
})
//playButton 开始播放
export function PlayButton(props): Override {
return {
onTap() {
setInterval(() => {
data.length += 1
}, 100)
data.isPlay = true
},
}
}
//主Group
export function ButtonGroup(props): Override {
const controls = useAnimation()
return {
drag: true,
dragConstraints: {
right: data.isControlsListOpen ? 162 : 309,
left: 0,
bottom: 198,
top: -429,
},
dragMomentum: false,
// pan
onPanEnd(event, info) {
const move = info.offset.x
// console.log(move)
if (move > 1) {
data.isRelease = 3
} else {
data.isRelease = 0
}
},
//tap
onTap() {
if (data.isRelease < 2) {
// console.log("ok")
data.isControlsListOpen = true
}
},
//drag
onDragEnd(event, info) {
const positionX = info.point.x
// console.log(positionX)
const limitX = data.isControlsListOpen ? 1 : 0
const limitY = data.isControlsListOpen ? 1 : 0
if (positionX < 76) {
controls.start({
x: 0,
transition: {
duration: 0.35,
},
})
} else if (positionX >= 76) {
controls.start({
x: data.isControlsListOpen ? 170 : 309,
transition: {
duration: 0.35,
},
})
}
},
animate: controls,
opacity: data.isPlay ? 1 : 0,
width: data.isControlsListOpen ? 184 : 40,
transition: {
duration: 0.25,
},
}
}
//controlsList
export function controlsList(): Override {
return {
animate: data.isControlsListOpen ? { opacity: 1 } : { opacity: 0 },
transition: {
duration: 0.2,
delay: 0.15,
},
}
}
//playMove
export function playMove(): Override {
return {
visible: data.isControlsListOpen ? false : true,
}
}
//userPhoto
export function userPhoto(): Override {
return {
animate: data.isControlsListOpen ? { opacity: 1 } : { opacity: 0 },
transition: {
duration: 0.2,
},
}
}
//bg
export function bg(): Override {
return {
animate: data.isControlsListOpen ? { width: 184 } : { width: 40 },
}
}
//closeButtonGroup
export function close(): Override {
return {
onTap() {
data.isPlay = false
},
}
}
//中间的播放浪
export function TapFrame(props): Override {
return {
animate: {
height: 2,
originY: 0,
transition: {
duration: 0.45,
flip: Infinity,
delay: props.name,
},
},
}
}
//进程圈
export function circle(props): Override {
if (data.length < 360) {
return {
length: data.length,
}
} else {
return {
length: 360,
}
}
}