EP152. “InspectorControls 侧栏与自定义背景色”
🔒 登录后可标记已读📌 并入 EP151 的提醒:前面几讲(EP149/EP150)的动画/过渡效果,做法都是让内容始终存在于 JSX/DOM 里,单纯靠切换 CSS class 来控制视觉上看不看得到,不是真的用条件判断动态增删 DOM 节点。作者说这是为了这门课的重点(WordPress 而不是 React)刻意做的简化;如果想学会「真正让 JSX 按条件添加/移除内容、同时还有平滑过渡动画」,可以研究 React 社区常用的 React Transition Group 这个包。
回到编辑器后台,给 Block 加一个右侧「Inspector」自定义设置面板,让站长可以给每个 Block 实例单独选背景色。用 WordPress 提供的 InspectorControls + PanelBody + PanelRow 三件套搭出右侧面板骨架,新增 bgColor 字符串属性存储选中的颜色;一开始用 WordPress 自带的 ColorPicker 组件试通,但发现它在侧栏这个窄空间里会挤出难看的横向滚动条,于是改用第三方 npm 包 react-color 的 ChromePicker 组件替换——顺带演示了「WordPress 项目里也能直接装用社区通用的 React 包,不是所有东西都得用官方组件」。最后把 bgColor 分别接到编辑器预览区和前台的最外层 <div> 的行内样式上。
涉及文件
wp-content/plugins/are-you-paying-attention/src/index.js(修改)wp-content/plugins/are-you-paying-attention/src/frontend.js(修改)wp-content/plugins/are-you-paying-attention/package.json(修改,新增react-color依赖)
代码实现
先用 npm 安装第三方颜色选择器包(终端命令):
npm install react-color
src/index.js(完整文件):
import "./index.scss"
import {TextControl, Flex, FlexBlock, FlexItem, Button, Icon, PanelBody, PanelRow, ColorPicker} from "@wordpress/components"
import {InspectorControls} from "@wordpress/block-editor"
import {ChromePicker} from "react-color"
(function() {
let locked = false
wp.data.subscribe(function() {
const results = wp.data.select("core/block-editor").getBlocks().filter(function(block) {
return block.name == "ourplugin/are-you-paying-attention" && block.attributes.correctAnswer == undefined
})
if (results.length && locked == false) {
locked = true
wp.data.dispatch("core/editor").lockPostSaving("noanswer")
}
if (!results.length && locked) {
locked = false
wp.data.dispatch("core/editor").unlockPostSaving("noanswer")
}
})
})()
wp.blocks.registerBlockType("ourplugin/are-you-paying-attention", {
title: "Are You Paying Attention?",
icon: "smiley",
category: "common",
attributes: {
question: {type: "string"},
answers: {type: "array", default: [""]},
correctAnswer: {type: "number", default: undefined},
bgColor: {type: "string", default: "#EBEBEB"}
},
edit: EditComponent,
save: function (props) {
return null
}
})
function EditComponent (props) {
function updateQuestion(value) {
props.setAttributes({question: value})
}
function deleteAnswer(indexToDelete) {
const newAnswers = props.attributes.answers.filter(function(x, index) {
return index != indexToDelete
})
props.setAttributes({answers: newAnswers})
if (indexToDelete == props.attributes.correctAnswer) {
props.setAttributes({correctAnswer: undefined})
}
}
function markAsCorrect(index) {
props.setAttributes({correctAnswer: index})
}
return (
<div className="paying-attention-edit-block" style={{backgroundColor: props.attributes.bgColor}}>
<InspectorControls>
<PanelBody title="Background Color" initialOpen={true}>
<PanelRow>
<ChromePicker color={props.attributes.bgColor} onChangeComplete={x => props.setAttributes({bgColor: x.hex})} disableAlpha={true} />
</PanelRow>
</PanelBody>
</InspectorControls>
<TextControl label="Question:" value={props.attributes.question} onChange={updateQuestion} style={{fontSize: "20px"}} />
<p style={{fontSize: "13px", margin: "20px 0 8px 0"}}>Answers:</p>
{props.attributes.answers.map(function (answer, index) {
return (
<Flex>
<FlexBlock>
<TextControl value={answer} onChange={newValue => {
const newAnswers = props.attributes.answers.concat([])
newAnswers[index] = newValue
props.setAttributes({answers: newAnswers})
}} />
</FlexBlock>
<FlexItem>
<Button onClick={() => markAsCorrect(index)}>
<Icon className="mark-as-correct" icon={props.attributes.correctAnswer == index ? "star-filled" : "star-empty"} />
</Button>
</FlexItem>
<FlexItem>
<Button isLink className="attention-delete" onClick={() => deleteAnswer(index)}>Delete</Button>
</FlexItem>
</Flex>
)
})}
<Button isPrimary onClick={() => {
props.setAttributes({answers: props.attributes.answers.concat([""])})
}}>Add another answer</Button>
</div>
)
}
src/frontend.js:把 bgColor 接到前台最外层 <div> 的行内样式:
function Quiz(props) {
// ...状态定义和 useEffect 不变(见 EP150)...
return (
<div className="paying-attention-frontend" style={{backgroundColor: props.bgColor}}>
{/* ...其余内容不变... */}
</div>
)
}
关键改动点:
import {InspectorControls} from "@wordpress/block-editor":右侧编辑器「检查器」面板的容器组件,来自跟@wordpress/components不同的另一个包@wordpress/block-editor——虽然没有真的执行过npm install这两个包,但@wordpress/scripts内建的 Webpack 配置认得这些包名,会自动转成读取浏览器全局已经加载好的 WordPress 脚本,不需要额外安装<InspectorControls>放在哪里都行(这里放在最外层<div>开头),WordPress 看到这个组件不会把它渲染在编辑器主体的左侧列,而是自动识别、搬到右侧检查器面板——这是这门课作者说「第一次看到这个效果时挺意外」的地方,不需要自己写任何定位/布局代码<PanelBody title="..." initialOpen={true}>:右侧面板里的一个可折叠分组,title是分组标题,initialOpen控制默认是展开还是收起<PanelRow>:PanelBody内部的一行内容容器,用来包住实际的设置控件(这里是颜色选择器)- 新增
bgColor: {type: "string", default: "#EBEBEB"}属性,默认值是浅灰色的十六进制色值 - 一开始用 WordPress 自带的
ColorPicker(来自@wordpress/components)验证链路能不能跑通,写法是<ColorPicker color={...} onChangeComplete={...} />——注意这个组件的变更事件 prop 名是onChangeComplete,不是常见的onChange,这是要特别记住的一个细节 onChangeComplete={x => props.setAttributes({bgColor: x.hex})}:回调函数拿到的参数(这里命名成x)是一个包含颜色信息的对象,x.hex是其中的十六进制色值字段——WordPress 自带的ColorPicker和后面换上的ChromePicker用的都是同一个字段名hex,接口一致,换组件时几乎不用改回调逻辑- 换成第三方包
react-color的ChromePicker:WordPress 自带的ColorPicker塞进右侧这条窄的检查器面板时,会挤出一条难看的横向滚动条、图标也放不下——于是改用社区常见的react-color包,这个包跟 WordPress 完全没关系,纯粹是 npm 上通用的 React 组件库,直接npm install react-color就能装、import {ChromePicker} from "react-color"就能用,用来演示「WordPress 环境下的 React 开发,其实也能直接用社区任何标准 React 包,不是只能用官方组件」 <ChromePicker color={...} onChangeComplete={...} disableAlpha={true} />:disableAlpha是ChromePicker专属的 prop,禁用透明度调节,因为站长这里只需要选纯色,不需要控制透明度- 编辑器预览区的最外层
<div>加style={{backgroundColor: props.attributes.bgColor}}(读取时要经过.attributes.这一层,跟其他属性一致) - 前台
frontend.js的最外层<div>同样加style={{backgroundColor: props.bgColor}}——但这里没有.attributes.这一层,因为前台组件通过{...data}展开语法把 attributes 数据直接摊平传成 props(EP148 已经讲过这个机制),如果照抄编辑器那边写成props.attributes.bgColor会读取不到值、样式不生效
[截图:Gutenberg 编辑器右侧检查器面板里的 Background Color 分组,展开显示 ChromePicker 颜色选择器]
Hook / Function 速查
| 名称 | 类型 | 用途 |
|---|---|---|
InspectorControls | @wordpress/block-editor 组件 | 把内部内容渲染到编辑器右侧检查器面板,而不是主编辑区 |
PanelBody | @wordpress/components 组件 | 检查器面板里的可折叠分组容器 |
PanelRow | @wordpress/components 组件 | PanelBody 内的一行内容容器 |
ColorPicker | @wordpress/components 组件 | WordPress 自带的颜色选择器,变更事件是 onChangeComplete 而非 onChange |
ChromePicker(来自 react-color) | 第三方 npm React 组件 | 仿 Chrome 浏览器风格的颜色选择器,界面更紧凑,适合窄侧栏 |
常见坑
- 以为
ColorPicker/ChromePicker的变更事件叫onChange——实际上是onChangeComplete,写成onChange不会报错但完全不会触发 - 在前台
frontend.js里读取颜色属性时照抄编辑器那边的写法props.attributes.bgColor——前台组件的 props 是通过展开语法摊平传入的,正确写法是直接props.bgColor,多写一层.attributes.会读取不到值 - CSS 属性名在 JS 对象里要写成驼峰式
backgroundColor,不是 CSS 原生的background-color——这是 React 内联样式对象(style={{...}})的通用规则,不是这个组件独有的 bgColor属性默认值设成undefined或空字符串——没有默认背景色,第一次插入 Block 时会显示成完全透明/无背景,不如给一个具体的浅灰色默认值更友好
延伸 / 后续讲座会用到
下一讲是这个 Block 类型的最后一讲,会加上文字对齐(左/中/右)的工具栏图标设置,做完就要换到下一个章节。
Sources
Udemy:
- Become a WordPress Developer: Unlocking Power With Code — Section 25, EP152(含 EP151 提醒并入)