SMACSS-Press

Avatar of Scott Fennell
Scott Fennell

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 $200 免费额度!

以下是 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-itempod-items。

这或多或少是我使用 CSS 针对 HTML 的理想设置。这是我将在本文中努力实现的模型。不幸的是,WordPress 的默认设置并非如此。

将 WordPress 转换为 SMACSS 风格

要让 WordPress 遵守这种格式,需要四种主要方法

  1. 非常快速简便的过滤器,只需几行代码就可以修改 WordPress 的输出内容。
  2. 告诉 WordPress 将某些类应用于我们注册的对象。
  3. 在自定义函数中养成良好的习惯(无论是插件代码还是 functions.php),以便它们能够轻松且可管理地接受定制类。
  4. 对 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_searchwidget_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 类应用于菜单的方法。

课后总结

我发现,随着时间的推移,一个项目中最难保持整洁的部分是样式表。函数、页面模板和侧边栏可能会来来去去,但样式表似乎永远存在,带着所有瑕疵!我认为,在一开始就进行一些分类可以帮助你在未来保持项目的可维护性。