The idea started with a reader wall I saw on another blog and a friend-links layout that handled regular readers in a more interesting way. An older plugin gave me the starting point, but its code no longer fit, so I reworked the approach for a current theme.

The feature is built as a shortcode. It queries approved comments, groups them by commenter email, and displays avatars with a tooltip showing the author name and comment count. Avatars link to the commenter's website when one is available. The queries skip password-protected posts and exclude one commenter, and the accumulated result is cached for an hour with a transient.

Add the shortcode and queries to the theme

Open functions.php in the active theme directory and add the following at the end of the file:

// Register and enqueue the readers wall CSS
function enqueue_readers_wall_styles() {
    global $post;
    if (is_a($post, 'WP_Post') && has_shortcode($post->post_content, 'readers_wall')) {
        wp_enqueue_style('readers-wall-style', get_template_directory_uri() . '/css/readers-wall.css', array(), '1.0.0');
    }
}
add_action('wp_enqueue_scripts', 'enqueue_readers_wall_styles');

// Helper function: generate ranking list
function generate_readers_list($title, $query, $limit) {
    global $wpdb;
    $output = '';

    // Cache query results with a transient
    $transient_key = 'readers_wall_' . md5($query);
    $wall = get_transient($transient_key);

    if (false === $wall) {
        $wall = $wpdb->get_results($query);
        set_transient($transient_key, $wall, 3600);
    }

    $output .= '<div class="readers-section">';
    $output .= '<h2 class="entry-title">' . esc_html($title) . ' TOP' . esc_html($limit) . '</h2>';

    if ($wall) {
        $output .= "<ul class='readers-list'>";
        foreach ($wall as $comment) {
            $avatar = get_avatar($comment->comment_author_email, 64, '', '', array('loading' => 'lazy'));
            $url = esc_url($comment->comment_author_url ? $comment->comment_author_url : "#");
            $author = esc_html($comment->comment_author);
            $count = intval($comment->cnt);
            // Use author name instead of email as the tooltip ID
            $tooltip_id = sanitize_title($author);

            $tooltip = "{$author}<br>评论数: {$count}";

            $output .= "<li>
                            <a rel='friend' target='_blank' href='{$url}' aria-describedby='tooltip-{$tooltip_id}'>
                                {$avatar}
                                <div class='tooltip' id='tooltip-{$tooltip_id}' role='tooltip'>{$tooltip}</div>
                            </a>
                        </li>";
        }
        $output .= "</ul>";
    } else {
        $output .= "<p>没有找到" . esc_html($title) . "数据。</p>";
    }

    $output .= '</div>';

    return $output;
}

// Shortcode function: readers wall
function readers_wall_shortcode() {
    global $wpdb;
    $output = '';

    // Overall comment ranking
    $query2 = $wpdb->prepare(
        "SELECT COUNT(comment_ID) AS cnt, comment_author, comment_author_url, comment_author_email
        FROM $wpdb->comments
        LEFT JOIN $wpdb->posts ON ($wpdb->posts.ID = $wpdb->comments.comment_post_ID)
        WHERE post_password = ''
        AND comment_approved = '1'
        AND comment_author != %s
        GROUP BY comment_author_email
        ORDER BY cnt DESC
        LIMIT %d",
        '段先森',
        12
    );
    $output .= generate_readers_list('评论总排行榜', $query2, 12);

    // Yearly comment ranking
    $query1 = $wpdb->prepare(
        "SELECT COUNT(comment_ID) AS cnt, comment_author, comment_author_url, comment_author_email
        FROM (
            SELECT * FROM $wpdb->comments
            LEFT JOIN $wpdb->posts ON ($wpdb->posts.ID = $wpdb->comments.comment_post_ID)
            WHERE comment_date BETWEEN DATE_SUB(NOW(), INTERVAL 1 YEAR) AND NOW()
            AND post_password = ''
            AND comment_approved = '1'
            AND comment_author != %s
        ) AS tempcmt
        GROUP BY comment_author_email
        ORDER BY cnt DESC
        LIMIT %d",
        '段先森',
        365
    );
    $output .= generate_readers_list('年度评论排行', $query1, 365);

    // Monthly comment ranking
    $query2 = $wpdb->prepare(
        "SELECT COUNT(comment_ID) AS cnt, comment_author, comment_author_url, comment_author_email
        FROM (
            SELECT * FROM $wpdb->comments
            LEFT JOIN $wpdb->posts ON ($wpdb->posts.ID = $wpdb->comments.comment_post_ID)
            WHERE DATE_FORMAT(comment_date, '%%Y-%%m') = DATE_FORMAT(NOW(), '%%Y-%%m')
            AND post_password = ''
            AND comment_approved = '1'
            AND comment_author != %s
        ) AS tempcmt
        GROUP BY comment_author_email
        ORDER BY cnt DESC
        LIMIT %d",
        '段先森',
        31
    );
    $output .= generate_readers_list('本月评论排行', $query2, 31);

    // Weekly comment ranking
    $query3 = $wpdb->prepare(
        "SELECT COUNT(comment_ID) AS cnt, comment_author, comment_author_url, comment_author_email
        FROM (
            SELECT * FROM $wpdb->comments
            LEFT JOIN $wpdb->posts ON ($wpdb->posts.ID = $wpdb->comments.comment_post_ID)
            WHERE YEARWEEK(DATE_FORMAT(comment_date, '%%Y-%%m-%%d')) = YEARWEEK(NOW())
            AND post_password = ''
            AND comment_approved = '1'
            AND comment_author != %s
        ) AS tempcmt
        GROUP BY comment_author_email
        ORDER BY cnt DESC
        LIMIT %d",
        '段先森',
        7
    );
    $output .= generate_readers_list('本周评论排行', $query3, 7);

    return $output;
}
add_shortcode('readers_wall', 'readers_wall_shortcode');

Create the stylesheet

In the theme directory, create or open the css folder and add readers-wall.css:

/* readers-wall.css */

/* Container styles */
.readers-section {
    margin-bottom: 30px;
}
.readers-section h2.entry-title {
    font-size: 24px;
    margin-bottom: 15px;
    color: #333;
}

/* Avatar list styles */
.readers-list {
    display: flex;
    flex-wrap: wrap;
    list-style: none;
    padding: 0;
    margin: 0;
}
.readers-list li {
    position: relative;
    margin: 10px;
    width: 50px; /* Adjust avatar size */
    height: 50px;
}
.readers-list li a {
    display: block;
    width: 100%;
    height: 100%;
    text-align: center;
    text-decoration: none;
    position: relative;
}
.readers-list li img {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
    transition: transform 0.3s ease;
}
.readers-list li a:hover img,
.readers-list li a:focus img {
    transform: scale(1.1);
}

/* Hover tooltip styles */
.readers-list li .tooltip {
    visibility: hidden;
    opacity: 0;
    width: 160px;
    background-color: rgba(0, 0, 0, 0.75);
    color: #fff;
    text-align: center;
    border-radius: 6px;
    padding: 8px;
    position: absolute;
    bottom: 60px; /* above avatar */
    left: 50%;
    transform: translateX(-50%);
    transition: opacity 0.3s ease;
    z-index: 10;
    font-size: 14px;
}
.readers-list li .tooltip::after {
    content: "";
    position: absolute;
    top: 100%; /* arrow points to avatar */
    left: 50%;
    margin-left: -5px;
    border-width: 5px;
    border-style: solid;
    border-color: rgba(0, 0, 0, 0.75) transparent transparent transparent;
}
.readers-list li:hover .tooltip,
.readers-list li a:focus .tooltip {
    visibility: visible;
    opacity: 1;
}

/* Responsive design */
@media (max-width: 600px) {
    .readers-list li {
        width: 40px;
        height: 40px;
    }
    .readers-section h2.entry-title {
        font-size: 20px;
    }
    .readers-list li .tooltip {
        width: 140px;
        font-size: 12px;
    }
}

Put the reader wall on a page

Create a new page in WordPress or edit an existing one, then insert the shortcode:

readers_wall

WordPress page with the readers wall shortcode

The CSS controls the avatar size, spacing, hover effect, tooltip position, and the smaller layout used on narrow screens. Adjust those values in readers-wall.css to match the rest of the theme.