framer-code-components-overrides

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Framer Code Development

Framer代码开发

Code Components vs Code Overrides

Code Components与Code Overrides对比

Code Components: Custom React components added to canvas. Support
addPropertyControls
.
Code Overrides: Higher-order components wrapping existing canvas elements. Do NOT support
addPropertyControls
.
Code Components:添加到画布中的自定义React组件。支持
addPropertyControls
Code Overrides:包裹现有画布元素的高阶组件。不支持
addPropertyControls

Required Annotations

必需的注解

Always include at minimum:
typescript
/**
 * @framerDisableUnlink
 * @framerIntrinsicWidth 100
 * @framerIntrinsicHeight 100
 */
Full set:
  • @framerDisableUnlink
    — Prevents unlinking when modified
  • @framerIntrinsicWidth
    /
    @framerIntrinsicHeight
    — Default dimensions
  • @framerSupportedLayoutWidth
    /
    @framerSupportedLayoutHeight
    any
    ,
    auto
    ,
    fixed
    ,
    any-prefer-fixed
至少始终包含以下注解:
typescript
/**
 * @framerDisableUnlink
 * @framerIntrinsicWidth 100
 * @framerIntrinsicHeight 100
 */
完整注解集合:
  • @framerDisableUnlink
    — 防止修改时解除关联
  • @framerIntrinsicWidth
    /
    @framerIntrinsicHeight
    — 默认尺寸
  • @framerSupportedLayoutWidth
    /
    @framerSupportedLayoutHeight
    — 可选值为
    any
    auto
    fixed
    any-prefer-fixed

Code Override Pattern

Code Overrides模式

typescript
import type { ComponentType } from "react"
import { useState, useEffect } from "react"

/**
 * @framerDisableUnlink
 */
export function withFeatureName(Component): ComponentType {
    return (props) => {
        // State and logic here
        return <Component {...props} />
    }
}
Naming: Always use
withFeatureName
prefix.
typescript
import type { ComponentType } from "react"
import { useState, useEffect } from "react"

/**
 * @framerDisableUnlink
 */
export function withFeatureName(Component): ComponentType {
    return (props) => {
        // 状态和逻辑写在这里
        return <Component {...props} />
    }
}
命名规则:始终使用
withFeatureName
前缀。

Code Component Pattern

Code Components模式

typescript
import { motion } from "framer-motion"
import { addPropertyControls, ControlType } from "framer"

/**
 * @framerDisableUnlink
 * @framerIntrinsicWidth 300
 * @framerIntrinsicHeight 200
 */
export default function MyComponent(props) {
    const { style } = props
    return <motion.div style={{ ...style }}>{/* content */}</motion.div>
}

MyComponent.defaultProps = {
    // Always define defaults
}

addPropertyControls(MyComponent, {
    // Controls here
})
typescript
import { motion } from "framer-motion"
import { addPropertyControls, ControlType } from "framer"

/**
 * @framerDisableUnlink
 * @framerIntrinsicWidth 300
 * @framerIntrinsicHeight 200
 */
export default function MyComponent(props) {
    const { style } = props
    return <motion.div style={{ ...style }}>{/* 内容 */}</motion.div>
}

MyComponent.defaultProps = {
    // 始终定义默认值
}

addPropertyControls(MyComponent, {
    // 控件配置写在这里
})

Critical: Font Handling

关键注意事项:字体处理

Never access font properties individually. Always spread the entire font object.
typescript
// ❌ BROKEN - Will not work
style={{
    fontFamily: props.font.fontFamily,
    fontSize: props.font.fontSize,
}}

// ✅ CORRECT - Spread entire object
style={{
    ...props.font,
}}
Font control definition:
typescript
font: {
    type: ControlType.Font,
    controls: "extended",
    defaultValue: {
        fontFamily: "Inter",
        fontWeight: 500,
        fontSize: 16,
        lineHeight: "1.5em",
    },
}
切勿单独访问字体属性。始终展开整个字体对象。
typescript
// ❌ 错误写法 - 无法正常工作
style={{
    fontFamily: props.font.fontFamily,
    fontSize: props.font.fontSize,
}}

// ✅ 正确写法 - 展开整个对象
style={{
    ...props.font,
}}
字体控件定义:
typescript
font: {
    type: ControlType.Font,
    controls: "extended",
    defaultValue: {
        fontFamily: "Inter",
        fontWeight: 500,
        fontSize: 16,
        lineHeight: "1.5em",
    },
}

Critical: Wrap State Updates in startTransition

关键注意事项:将状态更新包裹在startTransition中

All React state updates in Framer must be wrapped in
startTransition()
:
typescript
import { startTransition } from "react"

// ❌ WRONG - May cause issues in Framer's rendering pipeline
setCount(count + 1)

// ✅ CORRECT - Always wrap state updates
startTransition(() => {
    setCount(count + 1)
})
This is Framer-specific and prevents performance issues with concurrent rendering.
Framer中所有React状态更新都必须包裹在
startTransition()
中:
typescript
import { startTransition } from "react"

// ❌ 错误写法 - 可能在Framer的渲染管道中引发问题
setCount(count + 1)

// ✅ 正确写法 - 始终包裹状态更新
startTransition(() => {
    setCount(count + 1)
})
这是Framer特有的要求,可避免并发渲染时的性能问题。

Critical: Hydration Safety

关键注意事项:水合安全性

Framer pre-renders on server. Browser APIs unavailable during SSR.
Two-phase rendering pattern:
typescript
const [isClient, setIsClient] = useState(false)

useEffect(() => {
    setIsClient(true)
}, [])

if (!isClient) {
    return <Component {...props} /> // SSR-safe fallback
}

// Client-only logic here
Never access directly at render time:
  • window
    ,
    document
    ,
    navigator
  • localStorage
    ,
    sessionStorage
  • window.innerWidth
    ,
    window.innerHeight
Framer会在服务器端预渲染。SSR期间无法使用浏览器API。
两阶段渲染模式:
typescript
const [isClient, setIsClient] = useState(false)

useEffect(() => {
    setIsClient(true)
}, [])

if (!isClient) {
    return <Component {...props} /> // SSR安全的回退内容
}

// 客户端专属逻辑写在这里
切勿在渲染时直接访问以下内容:
  • window
    document
    navigator
  • localStorage
    sessionStorage
  • window.innerWidth
    window.innerHeight

Critical: Canvas vs Preview Detection

关键注意事项:画布与预览环境检测

typescript
import { RenderTarget } from "framer"

const isOnCanvas = RenderTarget.current() === RenderTarget.canvas

// Show debug only in editor
{isOnCanvas && <DebugOverlay />}
Use for:
  • Debug overlays
  • Disabling heavy effects in editor
  • Preview toggles
typescript
import { RenderTarget } from "framer"

const isOnCanvas = RenderTarget.current() === RenderTarget.canvas

// 仅在编辑器中显示调试内容
{isOnCanvas && <DebugOverlay />}
适用场景:
  • 调试覆盖层
  • 在编辑器中禁用重型效果
  • 预览切换

Property Controls Reference

属性控件参考

See references/property-controls.md for complete control types and patterns.
完整的控件类型和模式请查看references/property-controls.md

Common Patterns

常见模式

See references/patterns.md for implementations: shared state, keyboard detection, show-once logic, scroll effects, magnetic hover, animation triggers.
实现方案请查看references/patterns.md:共享状态、键盘检测、一次性显示逻辑、滚动效果、磁吸悬停、动画触发器。

Variant Control in Overrides

Overrides中的变体控制

Cannot read variant names from props (may be hashed). Manage internally:
typescript
export function withVariantControl(Component): ComponentType {
    return (props) => {
        const [currentVariant, setCurrentVariant] = useState("variant-1")

        // Logic to change variant
        setCurrentVariant("variant-2")

        return <Component {...props} variant={currentVariant} />
    }
}
无法从props中读取变体名称(可能已被哈希处理)。需在内部管理:
typescript
export function withVariantControl(Component): ComponentType {
    return (props) => {
        const [currentVariant, setCurrentVariant] = useState("variant-1")

        // 切换变体的逻辑
        setCurrentVariant("variant-2")

        return <Component {...props} variant={currentVariant} />
    }
}

Scroll Detection Constraint

滚动检测限制

Framer's scroll detection uses viewport-based IntersectionObserver. Applying
overflow: scroll
to containers breaks this detection.
For scroll-triggered animations, use:
typescript
const observer = new IntersectionObserver(
    (entries) => {
        entries.forEach((entry) => {
            if (entry.isIntersecting && !hasEntered) {
                setHasEntered(true)
            }
        })
    },
    { threshold: 0.1 }
)
Framer的滚动检测使用基于视口的IntersectionObserver。为容器设置
overflow: scroll
会破坏该检测功能。
对于滚动触发的动画,请使用:
typescript
const observer = new IntersectionObserver(
    (entries) => {
        entries.forEach((entry) => {
            if (entry.isIntersecting && !hasEntered) {
                setHasEntered(true)
            }
        })
    },
    { threshold: 0.1 }
)

WebGL in Framer

Framer中的WebGL

See references/webgl-shaders.md for shader implementation patterns including transparency handling.
着色器实现模式(包括透明度处理)请查看references/webgl-shaders.md

NPM Package Imports

NPM包导入

Standard import (preferred):
typescript
import { Component } from "package-name"
Force specific version via CDN when Framer cache is stuck:
typescript
import { Component } from "https://esm.sh/package-name@1.2.3?external=react,react-dom"
Always include
?external=react,react-dom
for React components.
标准导入(推荐):
typescript
import { Component } from "package-name"
当Framer缓存异常时,通过CDN强制使用特定版本:
typescript
import { Component } from "https://esm.sh/package-name@1.2.3?external=react,react-dom"
对于React组件,始终添加
?external=react,react-dom
参数。

Common Pitfalls

常见陷阱

IssueCauseFix
Font styles not applyingAccessing font props individuallySpread entire font object:
...props.font
Hydration mismatchBrowser API in renderUse
isClient
state pattern
Override props undefinedExpecting property controlsOverrides don't support
addPropertyControls
Scroll animation broken
overflow: scroll
on container
Use IntersectionObserver on viewport
Shader attach errorNull shader from compilation failureCheck
createShader()
return before
attachShader()
Component display nameNeed custom name in Framer UI
Component.displayName = "Name"
TypeScript
Timeout
errors
Using
NodeJS.Timeout
type
Use
number
instead — browser environment
问题原因修复方案
字体样式不生效单独访问字体属性展开整个字体对象:
...props.font
水合不匹配渲染时使用浏览器API使用
isClient
状态模式
Overrides属性未定义期望使用属性控件Overrides不支持
addPropertyControls
滚动动画失效容器设置了
overflow: scroll
在视口上使用IntersectionObserver
着色器附加错误编译失败导致着色器为null
attachShader()
前检查
createShader()
的返回值
组件显示名称需要在Framer UI中使用自定义名称
Component.displayName = "Name"
TypeScript
Timeout
错误
使用
NodeJS.Timeout
类型
改用
number
类型——当前为浏览器环境

Mobile Optimization

移动端优化

For particle systems and heavy animations:
  • Implement resize debouncing (500ms default)
  • Add size change threshold (15% minimum)
  • Handle orientation changes with dedicated listener
  • Use
    touchAction: "none"
    to prevent scroll interference
对于粒子系统和重型动画:
  • 实现防抖 resize(默认500ms)
  • 添加尺寸变化阈值(最小15%)
  • 使用专用监听器处理方向变化
  • 设置
    touchAction: "none"
    以防止滚动干扰

CMS Content Timing

CMS内容加载时机

CMS content loads asynchronously after hydration. Processing sequence:
  1. SSR: Placeholder content
  2. Hydration: React attaches
  3. CMS Load: Real content (~50-200ms)
Add delay before processing CMS data:
typescript
useEffect(() => {
    if (isClient && props.children) {
        const timer = setTimeout(() => {
            processContent(props.children)
        }, 100)
        return () => clearTimeout(timer)
    }
}, [isClient, props.children])
CMS内容会在水合完成后异步加载。处理顺序:
  1. SSR:占位内容
  2. 水合:React挂载
  3. CMS加载:真实内容(约50-200ms)
处理CMS数据前添加延迟:
typescript
useEffect(() => {
    if (isClient && props.children) {
        const timer = setTimeout(() => {
            processContent(props.children)
        }, 100)
        return () => clearTimeout(timer)
    }
}, [isClient, props.children])

Text Manipulation in Overrides

Overrides中的文本处理

Framer text uses deeply nested structure. Process recursively:
typescript
const processChildren = (children) => {
    if (typeof children === "string") {
        return processText(children)
    }
    if (isValidElement(children)) {
        return cloneElement(children, {
            ...children.props,
            children: processChildren(children.props.children)
        })
    }
    if (Array.isArray(children)) {
        return children.map(child => processChildren(child))
    }
    return children
}
Framer文本使用深度嵌套结构。需递归处理:
typescript
const processChildren = (children) => {
    if (typeof children === "string") {
        return processText(children)
    }
    if (isValidElement(children)) {
        return cloneElement(children, {
            ...children.props,
            children: processChildren(children.props.children)
        })
    }
    if (Array.isArray(children)) {
        return children.map(child => processChildren(child))
    }
    return children
}

Animation Best Practices

动画最佳实践

Separate positioning from animation:
typescript
<motion.div
    style={{
        position: "absolute",
        left: `${offset}px`,  // Static positioning
        x: animatedValue,     // Animation transform
    }}
/>
Split animation phases for natural motion:
typescript
// Up: snappy pop
transition={{ duration: 0.15, ease: [0, 0, 0.39, 2.99] }}

// Down: smooth settle
transition={{ duration: 0.15, ease: [0.25, 0.46, 0.45, 0.94] }}
将定位与动画分离:
typescript
<motion.div
    style={{
        position: "absolute",
        left: `${offset}px`,  // 静态定位
        x: animatedValue,     // 动画变换
    }}
/>
拆分动画阶段以实现自然运动:
typescript
// 向上:快速弹出
transition={{ duration: 0.15, ease: [0, 0, 0.39, 2.99] }}

// 向下:平滑回落
transition={{ duration: 0.15, ease: [0.25, 0.46, 0.45, 0.94] }}

Safari SVG Fix

Safari SVG修复

Force GPU acceleration for smooth SVG animations:
typescript
style={{
    willChange: "transform",
    transform: "translateZ(0)",
    backfaceVisibility: "hidden",
}}
强制开启GPU加速以实现流畅的SVG动画:
typescript
style={{
    willChange: "transform",
    transform: "translateZ(0)",
    backfaceVisibility: "hidden",
}}