详情页关注动效

视频展示:

关注.mp4 (190.65MB)

源文件:

头条动效.framerx.zip

代码展示:

关注动效部分Override

  1. import { Override, Data } from "framer"
  2. const data = Data({
  3. isGetApi: false,
  4. isOpen: false,
  5. isListOpen: false,
  6. })
  7. //关注
  8. export function concern(): Override {
  9. return {
  10. onTap() {
  11. ;(data.isOpen = true),
  12. setTimeout(() => {
  13. ;(data.isGetApi = true), (data.isListOpen = true)
  14. }, 2200)
  15. },
  16. visible: data.isGetApi ? false : true,
  17. animate: data.isGetApi
  18. ? {
  19. opacity: 0,
  20. }
  21. : {
  22. opacity: 1,
  23. },
  24. transition: {
  25. duration: 0.1,
  26. },
  27. }
  28. }
  29. //关注文字
  30. export function concernFont(): Override {
  31. return {
  32. animate: data.isOpen ? { opacity: 0 } : { opacity: 1 },
  33. }
  34. }
  35. //关注请求API-circle
  36. export function getApiCirlce(): Override {
  37. return {
  38. animate: data.isOpen ? { opacity: 1 } : { opacity: 0 },
  39. }
  40. }
  41. //已关注
  42. export function concerned(): Override {
  43. return {
  44. onTap() {
  45. setTimeout(() => {
  46. ;(data.isGetApi = false), (data.isListOpen = false)
  47. }, 2200)
  48. data.isOpen = false
  49. },
  50. animate: data.isGetApi
  51. ? {
  52. opacity: 1,
  53. x: -38,
  54. }
  55. : {
  56. opacity: 0,
  57. x: 0,
  58. },
  59. transition: {
  60. duration: 0.35,
  61. },
  62. }
  63. }
  64. //已关注关注文字
  65. export function concernedFont(): Override {
  66. return {
  67. animate: data.isOpen ? { opacity: 1 } : { opacity: 0 },
  68. }
  69. }
  70. //已关注请求API-circle
  71. export function getApiCirlced(): Override {
  72. return {
  73. animate: data.isOpen ? { opacity: 0 } : { opacity: 1 },
  74. }
  75. }
  76. //下拉箭头
  77. export function arrowGroup(): Override {
  78. return {
  79. onTap() {
  80. data.isListOpen = !data.isListOpen
  81. },
  82. animate: data.isGetApi
  83. ? {
  84. opacity: 1,
  85. }
  86. : {
  87. opacity: 0,
  88. },
  89. }
  90. }
  91. //箭头方向
  92. export function arrow(): Override {
  93. return {
  94. rotate: data.isListOpen ? 0 : 180,
  95. }
  96. }
  97. //内容
  98. export function content(): Override {
  99. const variants = {
  100. open: {
  101. y: 200,
  102. transition: {
  103. duration: 0.35,
  104. },
  105. },
  106. closed: {
  107. y: 0,
  108. transition: {
  109. duration: 0.25,
  110. },
  111. },
  112. }
  113. return {
  114. initial: "closed",
  115. animate: data.isListOpen ? "open" : "closed",
  116. variants: variants,
  117. }
  118. }

滚动显示动效 override

  1. import { Override, motionValue, Data } from "framer"
  2. //data
  3. const data = Data({
  4. isScrollToUser: false,
  5. })
  6. const scrollY = motionValue(0)
  7. scrollY.onChange(point => {
  8. console.log(point)
  9. if (point < -120) {
  10. data.isScrollToUser = true
  11. } else {
  12. data.isScrollToUser = false
  13. }
  14. })
  15. export function Scroll(): Override {
  16. return {
  17. contentOffsetY: scrollY,
  18. }
  19. }
  20. export function userInfo(): Override {
  21. return {
  22. animate: data.isScrollToUser ? { opacity: 1 } : { opacity: 0 },
  23. transition:{
  24. duration:0.15
  25. }
  26. }
  27. }
  28. export function Title(): Override {
  29. return {
  30. animate: data.isScrollToUser ? { opacity: 0 } : { opacity: 1 },
  31. transition: {
  32. duration: 0.15
  33. }
  34. }
  35. }

真实数据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: "全国电力行业唯一的官方报纸行业的龙头老大",
    },
})

悬浮播放按钮

视频讲解

1.mp4 (148.04MB)

源文件

头条动效_悬浮按钮.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,
        }
    }
}