WP DEVELOP

EP152. “InspectorControls 侧栏与自定义背景色”

首页 WordPress 开发课程 GUTENBERG BLOCK 入门(block.json) · EP152
约 23 分钟· #EP152#GUTENBERG BLOCK 入门(block.json)
🔒 登录后可标记已读

📌 并入 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-colorChromePicker 组件替换——顺带演示了「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-colorChromePicker:WordPress 自带的 ColorPicker 塞进右侧这条窄的检查器面板时,会挤出一条难看的横向滚动条、图标也放不下——于是改用社区常见的 react-color 包,这个包跟 WordPress 完全没关系,纯粹是 npm 上通用的 React 组件库,直接 npm install react-color 就能装、import {ChromePicker} from "react-color" 就能用,用来演示「WordPress 环境下的 React 开发,其实也能直接用社区任何标准 React 包,不是只能用官方组件」
  • <ChromePicker color={...} onChangeComplete={...} disableAlpha={true} />disableAlphaChromePicker 专属的 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 提醒并入)