详情页关注动效
视频展示:
源文件:
头条动效.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-circle
export 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-circle
export 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"
//data
const 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,
}
}
}