以下是 Scott Fennell 的客座文章。Scott 在 CSS-Tricks 上看到了一篇关于 WordPress 中类名的文章,并分享了一些关于控制而非放任自流的个人想法。他的理念是在可能的情况下强制执行更严格的 SMACSS 风格哲学。
我最近读了一篇关于 理解 WordPress CSS 类 的文章,作者指出
WordPress 具有极高的可定制性,可以修改这些默认类,但我们这里不会深入讨论。— @carlosinho
我掌握了一些关于如何做到的技巧,并乐于与您分享。
SMACSS 顿悟
我非常喜欢 SMACSS 手册。在所有推荐的优秀实践中,我最喜欢的是 类名规则。为了避免过度简化几页精彩内容,我将把要点提炼成一个例子
<div class="pod">
<h3 class="pod-header">heading</h3>
<ul class="pod-items">
<li class="pod-item"><a class="pod-item-link" href="#">Click me</a></li>
<li class="pod-item"><a class="pod-item-link" href="#">Click me</a></li>
</ul>
</div>
这并非 SMACSS 手册中的原文,但它代表了 SMACSCS 在我的工作流程中所代表的意义。值得注意的是
.pod
的每个组件都可以通过以pod-
为前缀的 CSS 类访问。- 具有父级到子级(一对多)关系的组件采用单数/复数命名方案,例如
pod-item
和pod-items。
这或多或少是我使用 CSS 针对 HTML 的理想设置。这是我将在本文中努力实现的模型。不幸的是,WordPress 的默认设置并非如此。
将 WordPress 转换为 SMACSS 风格
要让 WordPress 遵守这种格式,需要四种主要方法
- 非常快速简便的过滤器,只需几行代码就可以修改 WordPress 的输出内容。
- 告诉 WordPress 将某些类应用于我们注册的对象。
- 在自定义函数中养成良好的习惯(无论是插件代码还是 functions.php),以便它们能够轻松且可管理地接受定制类。
- 对 WordPress 核心进行相对较大的覆盖,但最终可能不值得付出努力。
应该不言而喻(但我们还是说一下):实际修改 WordPress 核心文件并非可行选项。
过滤器
修改 WordPress 附带的许多类的最简单方法是使用过滤器。假设我们有一段数据。它可能是文章标题(字符串)或文章类数组。这些数据正在传输到某个地方,可能是从数据库到浏览器,以便在前端显示,或者可能是从文章编辑器到数据库,以便存储。很多情况下,当数据在两个地方之间传输时,我们可以使用过滤器来修改它。以下是一个使用应用于 body 标签的类的示例
/**
* Extends the default WordPress body class.
*
* @param array $classes An array of css classes.
* @return array The array of css classes, filtered.
*/
function sjf_body_class( $classes ) {
// If the current post has a post thumbnail, append a body class.
if ( is_singular() && has_post_thumbnail() ) {
$classes[] = 'body-has-post-thumbnail';
}
return $classes;
}
// Apply these classes to the body tag.
add_filter( 'body_class', 'sjf_body_class' );
请注意,我们使用了一个条件语句,仅当这是一篇带有特色图片的单篇文章时才执行此操作。务必在任何情况下都返回 $classes
数组,否则所有其他模板将不会获得任何 body 类! 将此要点视为 O’Reilly 动物书籍中的“陷阱”图标。
我们可以将任何字符串添加到 classes 数组中,但如果要获取任何非硬编码的内容,应先使用 sanitize_html_class() 清理它。例如,我们可能想要添加一些 post_meta
值,例如,如果这篇文章被艺术指导成特殊的配色方案
/**
* Extends the default WordPress body class to include the accent color for this post.
*
* @param array $classes An array of css classes.
* @return array The array of css classes, plus a new class for accent-color.
*/
function sjf_body_class( $classes ) {
// If the current post has an accent color, append a body class.
if ( is_singular() ) {
$accent_color = get_post_meta( get_the_ID(), 'sjf_accent_color', TRUE );
$accent_color = sanitize_html_class( $accent_color );
if( ! empty( $accent_color ) ) {
$classes[] = "body-$accent_color";
}
}
return $classes;
}
// Apply these classes to the body tag.
add_filter( 'body_class', 'sjf_body_class' );
我们可以以几乎完全相同的方式修改应用于循环中文章的类数组。唯一的区别是,我们不是将函数添加到 body_class
过滤器中,而是将其添加到 post_class
中
/**
* Extends the default WordPress post class to include the accent color for this post.
*
* @param array $classes An array of css classes.
* @return array The array of css classes, plus a new class for accent-color.
*/
function sjf_post_class( $classes ) {
// If the current post has an accent color, append a post class.
if ( is_singular() ) {
$accent_color = get_post_meta( get_the_ID(), 'sjf_accent_color', TRUE );
$accent_color = sanitize_html_class( $accent_color );
if( ! empty( $accent_color ) ) {
$classes[] = "sjf-body-$accent_color-post";
}
}
return $classes;
}
// Apply these classes to the post tag.
add_filter( 'post_class', 'sjf_post_class' );
还有许多类似的钩子。请参阅 Adam Brown 的列表 并搜索“class”,以获取更多想法。一些最适合添加额外类的候选者是
保持代码简洁
请注意,我对于 SMACSS 的解释要求我的文章类与 body 类相同,只是附加了 -post
后缀。借助 current_filter() 函数,我们实际上可以使用完全相同的函数来运行 body 类和文章类,从而保持代码简洁
function sjf_body_post_class( $classes ) {
// If the current post has an accent color, add a class.
if ( is_singular() ) {
$accent_color = get_post_meta( get_the_ID(), 'sjf_accent_color', TRUE );
$accent_color = sanitize_html_class( $accent_color );
if( ! empty( $accent_color ) ) {
// If we are on the post_class filter, tack on a '-post'.
$suffix = '';
if( current_filter() == 'post_class' ) { $suffix = '-post'; }
$classes[] = "sjf-body-$accent_color$suffix";
}
}
return $classes;
}
// Apply these classes to the body tag.
add_filter( 'body_class', 'sjf_body_post_class' );
// Apply these classes to the post tag.
add_filter( 'post_class', 'sjf_body_post_class' );
过滤器是将新类应用于 WordPress 生成的项目的强大且简单的工具。但是,对于 WordPress 响应主题或插件注册该项目而生成的项目,该怎么办呢?
注册
考虑以下代码,用于 注册侧边栏
function sjf_widgets_init() {
register_sidebar( array(
'name' => __( 'Main Sidebar', 'sjf' ),
'id' => 'sjf-main-widgets',
'description' => __( 'Widgets in this area will be shown on all posts and pages.', 'sjf' ),
'before_title' => '<h1>',
'after_title' => '</h1>',
) );
}
add_action( 'widgets_init', 'sjf_widgets_init' );
简单明了。但是,如果我们的目标是将 SMACSS 风格的类应用于尽可能多的元素,这是一个多么浪费的机会!这里有很多地方可以插入类,但与前面的过滤器不同,我们处理的是一个类数组,而这种情况需要一个简单的类名字符串。对于此示例,假设设计要求根据登录状态应用不同的部件样式。我们可以这样做
function sjf_widgets_init() {
$slug = 'sjf-main';
if( is_user_logged_in() ) {
$logged = 'logged-in';
} else {
$logged = 'logged-out';
}
register_sidebar( array(
'name' => __( 'Main Sidebar', 'theme-slug' ),
'id' => "$slug-widgets",
'description' => __( 'Widgets in this area will be shown on all posts and pages.', 'sjf' ),
'before_widget' => "<div id='%s' class='widget $slug-widget $slug-widget-$logged_in $slug-widget-%s'>",
'after_widget' => '</div>',
'before_title' => "<h1 class='widget-title $slug-widget-title $slug-widget-title-$logged_in'>",
'after_title' => '</h1>',
) );
}
add_action( 'widgets_init', 'sjf_widgets_init' );
这里有几个方面值得关注
- 用户数据。 使用
$logged
,我们运行前面提到的技巧,查看这篇文章是否被艺术指导成特殊的配色方案。 sprintf()
。 使用%s
,我们提示 WordPress 用此类部件的短语名称替换这小部分内容。例如,widget_search
或widget_text
。- 排列组合。 为了能够使用 SMACSS,我们不仅添加
$color_class
类或$id
类并认为就完成了。我们还要考虑我们可能需要的不同组合。例如,如果我们需要在主侧边栏的红色调页面上定位搜索表单,我们就可以使用sjf-main-widget-red-widget_search
这个单一的 CSS 类来实现。
需要明确的是,我从前面的 post_meta
示例中转向的原因之一是,在注册部件时,还没有公开 文章数据。
有些设计可能足够微妙,足以证明为部件提供更详细的类。.org 存储库中有一些插件可以快速为每个部件添加类:每个部件 添加类。
在介绍了一些为 WordPress 生成的元素添加类的技巧之后,我们现在将看看自定义元素。
自定义函数
无论是在我们的 functions.php
文件中还是在插件文件中,我们都无疑会注册许多自定义函数。我为保持代码符合 SMACSS 风格制定了一套流程。考虑以下函数,它仅返回 hello world
function sjf_hello() {
return '<span class="sjf-hello">hello world</span>';
}
完全不符合 SMACSS 风格。如果我们想要以符合 SMACSS 风格的方式定位此元素,我们需要更多内容。例如,如果此元素用在本文开头的 .pod
示例中,我们希望它包含 .pod-sjf-hello
这样的类。无论是什么情况,我们都希望能够方便地注入尽可能多的必要类。我的做法如下
/**
* Get the string "hello world", wrapped and classed.
*
* @param array $classes An array of CSS classes.
* @return string The "hello world" string, wrapped and classed.
*/
function sjf_hello( $classes = array() ) {
// The namespace for this element.
$slug = 'sjf-hello';
// Prepend the slug to each class name, convert to a string.
$classes_str = sjf_prepend_slug( $classes, $slug );
return "<span class='$slug $classes_str'>hello world</span>";
}
请注意,我的函数现在接受 $classes
参数,它期望一个 CSS 类名数组。如果我将一组类传递给它,我不想担心在事先为它们添加前缀,因此它们在函数内部使用另一个函数 sjf_prepend_slug()
来添加前缀
/**
* Prepend a string to each member of an array.
*
* @param array $classes An array of CSS classes.
* @param string $slug A namespace to prepend to each class.
* @return string The $classes array, cleaned, slugged, and imploded.
*/
function sjf_prepend_slug( $classes, $slug ) {
$cleaned = array();
foreach( $classes as $class ) {
$cleaned []= sanitize_html_class( $slug . '-' . $class );
}
// Convert the array to a string, with a space between each class name.
$classes_str = implode( ' ', $cleaned );
return $classes_str;
}
该函数还负责清理所有类。我们可以将其用于主题或插件中的所有模板标签。
这个简单的流程可以帮助我们以最少的额外代码来确保所有自定义函数符合 SMACSS 风格。
覆盖
我们之前讨论过的所有技术在代码方面都非常高效:回报率。也就是说,我们用相对较少的代码获得了大量的 SMACSS 好处,这正是促使我写这篇文章的灵感。但是,我们还可以采取另一种方式,那就是用我们自己的方法覆盖核心函数,在过程中添加我们的 SMACSS 类。
我可能不建议仅仅为了添加 SMACSS 类而这样做,因为这可能需要很多代码,而更多的代码意味着更多的错误。相反,如果默认 WordPress 输出中有一些元素需要为我们的项目进行改造(也许是为了设计或功能方面的考虑),我们应该抓住这个机会,对其进行分类。
searchform.php
主题开发者第一个要处理的覆盖之一就是搜索表单。实现此操作的方法是在我们的主题中添加一个新的搜索表单,该表单位于一个名为 `searchform.php` 的文件中。但是,我希望能够在其他地方重用我的自定义搜索表单,而且我不喜欢在 `functions.php` 之外定义函数,所以我的 `searchform.php` 看起来像这样
/**
* sjf search form.
*
* @package WordPress
* @subpackage sjf
* @since sjf 1.0
*/
echo sjf_get_search_form();
我的 sjf_get_search_form()
位于 `functions.php` 中,看起来像这样
/**
* Returns a WordPress search form.
*
* Accepts arguments to inject CSS classes into the form, which this theme uses
* in order to comply with SMACSS. Passing dynamic class values for each
* instance would not be possible with the normal use of searchform.php.
*
* @param array $classes CSS classes for the form.
* @return string A search form.
*
* @since anchorage 1.0
*/
if( ! function_exists( 'sjf_get_search_form' ) ) {
function sjf_get_search_form( $classes = array() ) {
$slug = 'sjf-searchform';
// An array of CSS classes for the search form.
$classes = sjf_prepend_slug( $classes, $slug );
// Grab the search term to use as a watermark.
$placeholder = esc_attr__( 'Search', 'sjf' );
if( get_query_var( 's' ) ) {
$placeholder = esc_attr( get_query_var( 's' ) );
}
$action = esc_url( home_url( '/' ) );
$search_for = esc_html__( 'Search for:', 'sjf' );
$search_for_attr = esc_attr__( 'Search for:', 'sjf' );
$submit = esc_html__( 'Submit', 'sjf' );
$out ="
<form action='$action' class='$classes $slug sjf-form' method='get' role='search'>
<label class='$classes-label $slug-label sjf-label' for='s'><span class='screen-reader-text'>$search_for</span></label>
<input id='s' type='search' title='$search_for_attr' name='s' value='$placeholder' class='$classes-input $slug-input sjf-input'>
<input type='submit' value='$submit' class='$classes-submit $slug-submit sjf-submit'>
</form>
";
return $out;
}
}
这是一个很优雅的表单!表单本身和其中的每个元素都以我喜欢的 SMACSS 式方式可以访问。我认为大多数开发人员习惯于替换默认的表单,所以可能在添加一些类的时候不会太麻烦。
对于其他组件来说,可能无法做到这一点。我曾经对整个评论模板进行了类似的努力,最终花了 500 行代码才完成。可行,但不一定值得。
导航菜单 walker 类:PHP 类
正如我之前提到的,应用于导航菜单项的类可以被过滤,但如果我们想要更精细的控制,也许是将类应用于链接本身、子菜单或我们选择的图标解决方案中的某些字形,WordPress 为我们提供了一种深入了解的方法。walker 类是相当复杂的,可能需要一个专门的教程。网上有一些很好的例子,请记住,这是一种在可想象的最精细的级别上将 CSS 类应用于菜单的方法。
课后总结
我发现,随着时间的推移,一个项目中最难保持整洁的部分是样式表。函数、页面模板和侧边栏可能会来来去去,但样式表似乎永远存在,带着所有瑕疵!我认为,在一开始就进行一些分类可以帮助你在未来保持项目的可维护性。
嘿,伙计,文章写得棒!我刚好在想这个,因为我打算从头开始重新开发我的网站。我还在看 BEM。当我阅读这篇文章时,我意识到很多功能可以用页面/文章模板来实现。基于 WP 的层次结构,模板名称会作为类输出,我相信你已经知道了。
我个人认为,如果这种事情对开发人员很重要,而你必须使用这种黑客技术和覆盖 WordPress 功能的方式来实现我们想要的,那么也许 WordPress 不是最理想的解决方案。
我认为这不是那种负面的黑客攻击和覆盖,而是利用了 WordPress 的内置钩子,这些钩子就是专门设计用来这样使用的。
非常棒的文章
我一直想知道 wp 的可编辑程度有多深,当有更多东西要更改时,似乎要做的很多。但是,要让你的网站正常工作/看起来不错,永远不会像那样简单。
感谢你写这篇文章,但这似乎是一个非常糟糕的想法!我仔细阅读了整篇文章,但我仍然不太明白你在做什么 - 而这正是问题所在。
如果你在这个项目上孤军奋战,那就没问题 - 随你高兴。但如果其他人要接手你的代码,那么这将会是一个巨大的陷阱!
在我看来,所有代码都应该在性能和可读性之间取得平衡 - 而这似乎两者都没有做到。
这让我想起一个关于英国开发人员的故事,他拒绝在他的 CSS 中使用美式拼写“color”,因此创建了一个脚本在编译时将其从他喜欢的拼写“colour”中转换出来。
为什么不直接使用“color”呢?为什么不直接使用 WordPress 类呢?在我看来,这有点像代码强迫症 (OCDing?)。好吧,打油诗就说到这里...
SMACSS 很棒。
DRY 很棒。
但请记住,KISS!
(不是美国摇滚乐队)
嗨,Steve,
感谢你阅读我的文章!
我同意你说的 KISS 是一个伟大的美德,而我写这篇文章是为了实现 KISS,而不是为了放弃它。
我认为,Web 项目中最具挑战性的复杂性来自不合适的 CSS 选择器,尤其是在经过几轮客户修改之后。SMACCS 帮助你在样式表中保持简单性。
如果你说我的 php 函数很复杂,我会辩解说,大多数函数只有 5-10 行代码。如果它们看起来很模糊,别担心,这篇文章本身不是关于 php 函数的介绍。
如果你在阅读完这篇文章后仍然不明白什么是 SMACSS,别担心,这不是一篇 SMACSS 教程。文章中链接了一个很棒的 SMACSS 教程。
如果你在阅读完这篇文章后仍然不明白什么是 WordPress 类以及如何修改它们,我表示歉意,我偏离了目标。
关于你说如果你是团队合作的话,这将是一个陷阱,你的意思是?SMACSS 是一个众所周知的约定。我更愿意进入一个 SMACSS 项目,而不是进入某个人的宠物命名方案。
关于你说性能方面,你看到了什么性能下降的问题?标记中多了一些类?服务器上的逻辑稍微多了一点?我认为这里没有相关的性能下降。
我同意为了实现 SMACSS 化 WordPress,可能会引入过多的复杂性,并且我在文章中承认了一些例子。
祝你工作顺利,我希望如果我有幸再次在这里分享我的见解,你会发现它更有用。
不错的代码
我可能错了,但似乎小部件代码示例中可能存在错误
$logged_in
的实例不应该只是$logged
才能与变量匹配吗比如这个
class='widget $slug-widget $slug-widget-$logged_in $slug-widget-%s'
应该改为这个
class='widget $slug-widget $slug-widget-$logged $slug-widget-%s'
谢谢!