framer-code-components-overrides
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFramer Code Development
Framer代码开发
Code Components vs Code Overrides
Code Components与Code Overrides对比
Code Components: Custom React components added to canvas. Support .
addPropertyControlsCode Overrides: Higher-order components wrapping existing canvas elements. Do NOT support .
addPropertyControlsCode Components:添加到画布中的自定义React组件。支持。
addPropertyControlsCode Overrides:包裹现有画布元素的高阶组件。不支持。
addPropertyControlsRequired Annotations
必需的注解
Always include at minimum:
typescript
/**
* @framerDisableUnlink
* @framerIntrinsicWidth 100
* @framerIntrinsicHeight 100
*/Full set:
- — Prevents unlinking when modified
@framerDisableUnlink - /
@framerIntrinsicWidth— Default dimensions@framerIntrinsicHeight - /
@framerSupportedLayoutWidth—@framerSupportedLayoutHeight,any,auto,fixedany-prefer-fixed
至少始终包含以下注解:
typescript
/**
* @framerDisableUnlink
* @framerIntrinsicWidth 100
* @framerIntrinsicHeight 100
*/完整注解集合:
- — 防止修改时解除关联
@framerDisableUnlink - /
@framerIntrinsicWidth— 默认尺寸@framerIntrinsicHeight - /
@framerSupportedLayoutWidth— 可选值为@framerSupportedLayoutHeight、any、auto、fixedany-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 prefix.
withFeatureNametypescript
import type { ComponentType } from "react"
import { useState, useEffect } from "react"
/**
* @framerDisableUnlink
*/
export function withFeatureName(Component): ComponentType {
return (props) => {
// 状态和逻辑写在这里
return <Component {...props} />
}
}命名规则:始终使用前缀。
withFeatureNameCode 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 hereNever access directly at render time:
- ,
window,documentnavigator - ,
localStoragesessionStorage - ,
window.innerWidthwindow.innerHeight
Framer会在服务器端预渲染。SSR期间无法使用浏览器API。
两阶段渲染模式:
typescript
const [isClient, setIsClient] = useState(false)
useEffect(() => {
setIsClient(true)
}, [])
if (!isClient) {
return <Component {...props} /> // SSR安全的回退内容
}
// 客户端专属逻辑写在这里切勿在渲染时直接访问以下内容:
- 、
window、documentnavigator - 、
localStoragesessionStorage - 、
window.innerWidthwindow.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 to containers breaks this detection.
overflow: scrollFor 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 for React components.
?external=react,react-dom标准导入(推荐):
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-domCommon Pitfalls
常见陷阱
| Issue | Cause | Fix |
|---|---|---|
| Font styles not applying | Accessing font props individually | Spread entire font object: |
| Hydration mismatch | Browser API in render | Use |
| Override props undefined | Expecting property controls | Overrides don't support |
| Scroll animation broken | | Use IntersectionObserver on viewport |
| Shader attach error | Null shader from compilation failure | Check |
| Component display name | Need custom name in Framer UI | |
TypeScript | Using | Use |
| 问题 | 原因 | 修复方案 |
|---|---|---|
| 字体样式不生效 | 单独访问字体属性 | 展开整个字体对象: |
| 水合不匹配 | 渲染时使用浏览器API | 使用 |
| Overrides属性未定义 | 期望使用属性控件 | Overrides不支持 |
| 滚动动画失效 | 容器设置了 | 在视口上使用IntersectionObserver |
| 着色器附加错误 | 编译失败导致着色器为null | 在 |
| 组件显示名称 | 需要在Framer UI中使用自定义名称 | |
TypeScript | 使用 | 改用 |
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 to prevent scroll interference
touchAction: "none"
对于粒子系统和重型动画:
- 实现防抖 resize(默认500ms)
- 添加尺寸变化阈值(最小15%)
- 使用专用监听器处理方向变化
- 设置以防止滚动干扰
touchAction: "none"
CMS Content Timing
CMS内容加载时机
CMS content loads asynchronously after hydration. Processing sequence:
- SSR: Placeholder content
- Hydration: React attaches
- 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内容会在水合完成后异步加载。处理顺序:
- SSR:占位内容
- 水合:React挂载
- 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",
}}