这篇文章提供一段代码,给 Bricks 编辑器配置几个自定义键盘快捷键,让建站工作流更快一些。
| 按键 | 动作 |
|---|---|
s |
插入区块(Section),并自动激活其内部的容器(Container) |
c |
插入容器(Container) |
b |
插入块(Block) |
d |
插入 Div |
t |
插入文本(Basic Text) |
h |
插入标题(Heading) |
i |
插入图片(Image) |
r |
插入富文本(Rich Text) |
l |
插入文本链接(Text Link) |
w |
用配置的包裹元素包裹选中的元素(默认:Block) |
Alt+H |
切换 :hover 伪类 |
| 有了这些快捷键,在 Bricks 里搭建页面会非常快。 |
另外,这段代码还把 Bricks 的默认行为改了:结构面板里原本单击即可重命名元素(个人认为这个交互很糟糕),改为双击才可重命名。
注意:
-
单键快捷键(s、c、b、d、t、h、i、r、l、w)只在没有按下任何修饰键时生效。
-
在表单输入框、CodeMirror 编辑器里打字时,或命令面板(command palette)打开时,所有快捷键都会禁用。
-
快捷键在构建器主面板和画布 iframe 里都有效。
-
w键遵循 Bricks 的builderWrapElement设置(与 CMD/CTRL+Shift+P 相同)。
第一步:修改 functions.php
编辑子主题的 functions.php。
把:
add_action( 'wp_enqueue_scripts', function() {
// Enqueue your files on the canvas & frontend, not the builder panel. Otherwise custom CSS might affect builder)
if ( ! bricks_is_builder_main() ) {
wp_enqueue_style( 'bricks-child', get_stylesheet_uri(), ['bricks-frontend'], filemtime( get_stylesheet_directory() . '/style.css' ) );
}
} );
替换为:
add_action( 'wp_enqueue_scripts', function() {
// Enqueue your files on the canvas & frontend, not the builder panel. Otherwise custom CSS might affect builder)
if ( ! bricks_is_builder_main() ) {
wp_enqueue_style( 'bricks-child', get_stylesheet_uri(), ['bricks-frontend'], filemtime( get_stylesheet_directory() . '/style.css' ) );
}
// Enqueue builder keyboard shortcuts (Alt+H to toggle :hover, single key shortcuts)
if ( bricks_is_builder_main() ) {
wp_enqueue_script(
'bl-builder-shortcuts',
get_stylesheet_directory_uri() . '/js/builder-shortcuts.js',
array( 'bricks-builder' ),
filemtime( get_stylesheet_directory() . '/js/builder-shortcuts.js' ),
true,
);
}
} );
相关阅读:
第二步:创建 builder-shortcuts.js
在子主题目录里新建一个名为 js 的文件夹。
在 js 文件夹里创建文件 builder-shortcuts.js,代码如下:
( function () {
/**
* Single-key shortcuts to insert elements.
*
* s = Section, c = Container, b = Block, d = Div,
* t = Text (Basic), h = Heading, i = Image,
* r = Rich Text, l = Text Link,
* w = Wrap selected element(s) with configured wrap element
*/
var ELEMENT_SHORTCUTS = {
KeyS: 'section',
KeyC: 'container',
KeyB: 'block',
KeyD: 'div',
KeyT: 'text-basic',
KeyH: 'heading',
KeyI: 'image',
KeyR: 'text',
KeyL: 'text-link',
};
/* -----------------------------------------------------------------------
* Vue reference caching (lazy-init).
*
* The Bricks Vue app, its globalProperties and $_state are stable
* references for the entire builder session. We cache them on first
* access instead of querying the DOM on every keypress.
* ----------------------------------------------------------------------- */
var _gp = null;
var _state = null;
/**
* Return the cached Vue global properties, initialising on first call.
*/
function gp() {
if ( ! _gp ) {
var app = window.top.document.querySelector( '.brx-body' );
if ( app && app.__vue_app__ ) {
_gp = app.__vue_app__.config.globalProperties;
_state = _gp.$_state;
}
}
return _gp;
}
/* -----------------------------------------------------------------------
* Element lookup helper.
*
* Prefers Bricks' built-in $_getDynamicElementById (likely O(1)) and
* falls back to an array search on $_dynamicElements.value.
* ----------------------------------------------------------------------- */
/**
* Look up an element by its ID using the fastest available method.
*
* @param {string} id Element ID.
* @return {Object|null} The element object or null.
*/
function getElementById( id ) {
var props = gp();
if ( ! props ) {
return null;
}
// Bricks provides $_getDynamicElementById for fast lookups.
if ( typeof props.$_getDynamicElementById === 'function' ) {
return props.$_getDynamicElementById( id );
}
// Fallback: linear search through the elements array.
var elements = props.$_dynamicElements && props.$_dynamicElements.value;
return elements
? elements.find( function ( el ) { return el.id === id; } )
: null;
}
/* -----------------------------------------------------------------------
* Editable-target guard.
* ----------------------------------------------------------------------- */
/**
* Check if the event target is an editable field where typing should be allowed.
* Readonly inputs inside the structure panel are NOT considered editable (the user
* is just selecting an element, not renaming it).
*/
function isEditableTarget( e ) {
var tag = e.target.tagName;
if ( tag === 'INPUT' ) {
// Allow shortcuts on readonly structure-panel label inputs.
if ( e.target.readOnly && e.target.closest( '.structure-item .title' ) ) {
return false;
}
return true;
}
if ( tag === 'TEXTAREA' || e.target.isContentEditable ) {
return true;
}
// CodeMirror editor
if ( e.target.closest && e.target.closest( '.CodeMirror' ) ) {
return true;
}
return false;
}
/* -----------------------------------------------------------------------
* Shortcut handlers — action-only (guard logic lives in the dispatcher).
* ----------------------------------------------------------------------- */
/**
* Toggle :hover pseudo-class.
*/
function toggleHover( e ) {
e.preventDefault();
if ( ! _state ) {
return;
}
_state.pseudoClassActive = _state.pseudoClassActive === ':hover' ? undefined : ':hover';
_state.activeSelector = undefined;
_state.showElementClasses = false;
// Ensure pseudo-classes panel is visible (same as watcher behavior).
if ( _state.pseudoClassActive ) {
localStorage.setItem( 'brx_show_pseudo_classes', 'true' );
}
}
/**
* Walk up the element tree from the active element to find its ancestor section.
* Returns the section element object, or null if not found.
*/
function findAncestorSection() {
if ( ! _state || ! _state.activeElement ) {
return null;
}
var current = _state.activeElement;
while ( current ) {
if ( current.name === 'section' ) {
return current;
}
if ( ! current.parent ) {
return null;
}
current = getElementById( current.parent );
}
return null;
}
/**
* Insert an element by name via the matching ELEMENT_SHORTCUTS entry.
*/
function insertElement( e ) {
var elementName = ELEMENT_SHORTCUTS[ e.code ];
if ( ! elementName ) {
return;
}
e.preventDefault();
var props = gp();
var el = props.$_createElement( { name: elementName } );
// When inserting a section, place it immediately after the current section
// (or the section that contains the active element). Bricks' addNewElement
// overwrites the index for sections, so we temporarily set the active element
// to the ancestor section and enable insertAfter.
if ( elementName === 'section' ) {
var section = findAncestorSection();
if ( section ) {
_state.activeId = section.id;
_state.insertAfter = true;
props.$_addNewElement( { element: el } );
_state.insertAfter = false;
return;
}
}
props.$_addNewElement( { element: el } );
}
/**
* Wrap selected element(s) with the configured wrap element (default: Block).
* Respects the same builderWrapElement setting as Bricks' CMD/CTRL+Shift+P.
*/
function wrapWithBlock( e ) {
if ( ! _state.activeElement ) {
return;
}
e.preventDefault();
var wrapType = ( window.bricksData && window.bricksData.builderWrapElement ) || 'block';
gp().$_wrapInNestable( null, wrapType );
}
/* -----------------------------------------------------------------------
* Unified keyboard dispatcher.
*
* A single keydown listener per document replaces the previous three.
* Guard logic (modifier keys, editable targets, command palette) is
* checked once, then the appropriate handler is called.
* ----------------------------------------------------------------------- */
function handleKeydown( e ) {
// Never intercept typing in form fields.
if ( isEditableTarget( e ) ) {
return;
}
// Alt+H — toggle :hover (allows Alt modifier, blocks others).
if ( e.altKey && e.code === 'KeyH' && ! e.ctrlKey && ! e.metaKey && ! e.shiftKey && ! e.repeat ) {
gp();
toggleHover( e );
return;
}
// Everything below requires NO modifier keys.
if ( e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.repeat ) {
return;
}
if ( ! gp() || ! _state ) {
return;
}
// Don't act when the command palette is open.
if ( _state.showCommandPalette ) {
return;
}
if ( e.code === 'KeyW' ) {
wrapWithBlock( e );
return;
}
if ( ELEMENT_SHORTCUTS[ e.code ] ) {
insertElement( e );
}
}
/**
* Attach the unified keydown listener to a document (main or iframe).
*/
function attachListeners( doc ) {
doc.addEventListener( 'keydown', handleKeydown );
}
/* -----------------------------------------------------------------------
* Structure panel: require double-click to edit element labels.
*
* Bricks' Vue click handler sets a reactive `readonly` variable to false
* on every single click, and Vue then removes the DOM readonly attribute
* in its render cycle.
*
* Strategy: a MutationObserver always reverts readonly removal
* immediately. When we detect a double-click (via the dblclick event),
* we temporarily pause the observer, explicitly remove readonly, and
* resume the observer after a short delay.
* ----------------------------------------------------------------------- */
function patchStructureLabelClick() {
function applyPatch( panel ) {
var paused = false;
// MutationObserver: always revert readonly removal while not paused.
var attrObserver = new MutationObserver( function ( mutations ) {
if ( paused ) {
return;
}
for ( var i = 0; i < mutations.length; i++ ) {
var m = mutations[ i ];
if (
m.type === 'attributes' &&
m.attributeName === 'readonly' &&
m.target.tagName === 'INPUT' &&
! m.target.hasAttribute( 'readonly' ) &&
m.target.closest( '.structure-item .title' )
) {
m.target.setAttribute( 'readonly', '' );
m.target.readOnly = true;
}
}
} );
attrObserver.observe( panel, {
attributes: true,
attributeFilter: [ 'readonly' ],
subtree: true,
} );
// On double-click: pause the observer, explicitly enter edit mode.
panel.addEventListener( 'dblclick', function ( e ) {
var input = e.target.closest( '.structure-item .title input[type="text"]' );
if ( ! input ) {
return;
}
// Pause observer so our changes aren't reverted.
paused = true;
// Remove readonly to enter edit mode.
input.removeAttribute( 'readonly' );
input.readOnly = false;
input.select();
// Resume observer after Vue has settled.
setTimeout( function () {
paused = false;
}, 200 );
}, true );
}
// The structure panel may not exist yet. Watch for it.
var existing = document.getElementById( 'bricks-structure' );
if ( existing ) {
applyPatch( existing );
return;
}
var observer = new MutationObserver( function () {
var panel = document.getElementById( 'bricks-structure' );
if ( panel ) {
applyPatch( panel );
observer.disconnect();
}
} );
observer.observe( document.body, { childList: true, subtree: true } );
}
/* -----------------------------------------------------------------------
* Auto-expand new Sections and select their Container.
*
* Wraps $_addNewElement so that when a Section is added (via any method —
* keyboard shortcut, + button, command palette) the new Section is
* expanded in the structure panel and its auto-created Container becomes
* the active element.
*
* Instead of relying on scrollToElementId (which only works when Bricks'
* structureAutoSync is enabled), we click the DOM toggle arrow directly,
* matching the approach used by Advanced Themer.
* ----------------------------------------------------------------------- */
function patchAddNewElementForSections() {
var props = gp();
if ( ! props || ! props.$_addNewElement ) {
return;
}
var original = props.$_addNewElement;
// Idempotency guard — avoid double-wrapping if the script runs twice.
if ( original.__bl_patched ) {
return;
}
props.$_addNewElement = function () {
var result = original.apply( this, arguments );
// Only act when a section was just created.
if ( ! result || result.name !== 'section' ) {
return result;
}
var sectionId = result.id;
// Wait one tick for Vue / Xp() to finish creating nestable children.
setTimeout( function () {
var section = getElementById( sectionId );
if ( ! section || ! section.children || ! section.children.length ) {
return;
}
// First child is the auto-created Container.
var containerId = section.children[ 0 ];
// Expand the section in the structure panel by clicking its toggle
// arrow. This works regardless of the structureAutoSync setting.
var toggle = document.querySelector(
'#bricks-structure .element[data-id="' + sectionId + '"] .toggle'
);
if ( toggle && toggle.dataset.name === 'arrow-right' ) {
toggle.click();
}
// Select the container.
_state.activeId = containerId;
_state.activePanel = 'element';
}, 0 );
return result;
};
props.$_addNewElement.__bl_patched = true;
}
/* -----------------------------------------------------------------------
* Initialisation.
* ----------------------------------------------------------------------- */
// Listen on main document.
attachListeners( document );
// Also listen inside the builder canvas iframe (where focus usually is).
var iframe = document.getElementById( 'bricks-builder-iframe' );
if ( iframe ) {
iframe.addEventListener( 'load', function () {
try {
attachListeners( iframe.contentDocument );
} catch ( err ) {
// Same-origin policy — should not happen on local dev.
}
} );
// Iframe may already be loaded.
try {
if ( iframe.contentDocument && iframe.contentDocument.readyState === 'complete' ) {
attachListeners( iframe.contentDocument );
}
} catch ( err ) {
// Ignore.
}
}
// Apply structure panel label patch.
patchStructureLabelClick();
// Patch addNewElement to auto-select container inside new sections.
// Retries until Vue is fully mounted and the global property is available.
function initPatches() {
if ( ! gp() || ! gp().$_addNewElement ) {
setTimeout( initPatches, 100 );
return;
}
patchAddNewElementForSections();
}
initPatches();
} )();
保存后重新加载构建器(Reload the builder)即可生效。
别忘了 Bricks 自带的默认快捷键。配合上面的自定义快捷键,下面这四个默认快捷键尤其好用:
💡 译者注:这段 JavaScript 代码的功劳归 Claude(原文结尾注明 “Credit for the JavaScript code: Claude.”)。代码依赖 Bricks 构建器内部的 Vue 应用全局对象(
__vue_app__、$_state、$_addNewElement等),属于对 Bricks 内部 API 的调用,升级 Bricks 版本后如失效,需按新版内部结构调整。
哪些快捷键值得改(适用场景)
最值得动的是每天重复的高频操作:比如把「前端预览」从 Ctrl+Shift+V 改回无格式粘贴、腾出顺手组合键;还有人把保存并关闭、复制样式、切换断点设成单手能按的键。误区:一是和系统/浏览器快捷键撞车(如 Ctrl+W、Ctrl+T)会出事,改前先查占用;二是改完没文档化,换设备或队友接手就懵。只改真正高频的几条,别把键盘改得认不出。
延伸阅读
- 想要现成外贸独立站,从零搭出能收询盘的站?看 铲子铺 chanzipu 的外贸建站教程(同用 Bricks)
常见问题(FAQ)
改快捷键会覆盖系统快捷键吗? 可能,改前先确认组合键没被浏览器/系统占用(Ctrl+W、Ctrl+T 别碰)。
怎么让团队统一? 把快捷键脚本和说明一起放进主题/片段管理,新人不用来回问。
改完不生效先查什么? 确认 JS 在构建器页面加载了、且组合键没被别的脚本拦截。
只改高频操作还是全改? 只改每天重复的那几条,改太多反而记不住、易撞键。
- 相关阅读:Bricks Setup Guide
延伸阅读
Bricks 查询循环输出 ACF 图片字段的 Alt、Caption、标题数据
教程:Bricks 查询循环输出 ACF 图片字段的 Alt、Caption、标题数据——acf 附完整代码可直接复用,适合外贸独立站与 WordPress 开发者。
tutorialBricks 中输出 ACF Relationship 关联文章的数量
教程:Bricks 中输出 ACF Relationship 关联文章的数量——acf 附完整代码可直接复用,适合外贸独立站与 WordPress 开发者。
tutorialACF Relationship 字段 + Bricks 查询循环:显示关联文章
教程:ACF Relationship 字段 + Bricks 查询循环:显示关联文章——acf 附完整代码可直接复用,适合外贸独立站与 WordPress 开发者。
tutorial把任意自定义 WP_Query 循环接入 Bricks 查询循环
教程:把任意自定义 WP_Query 循环接入 Bricks 查询循环——Bricks 附完整代码可直接复用,适合外贸独立站与 WordPress 开发者。
