Python Flask小项目:如何实现漫画阅读网站的响应式瀑布流与实时过滤搜索?

GitHub Product

Flask 练手项目,真的很小,所以没写注释,结构清晰,前端使用了 Bootstrap4, List.js, Viewer.js, Masonry, MDUI, 和imagesLoaded.

所以,支持动态响应式瀑布流和实时过滤搜索。

大佬们多给意见,毕竟 Flask 第一个项目,从开始学到做花了一天多 ~~~///(^v^)\~~~


Python Flask小项目:如何实现漫画阅读网站的响应式瀑布流与实时过滤搜索?

41 回复

挺喜欢这个风格的


Flask漫画网站:瀑布流布局 + 实时搜索的实现方案

核心思路:前端用Masonry.js做瀑布流,通过AJAX与Flask后端交互,搜索用JavaScript监听输入并过滤数据。

1. 后端Flask应用 (app.py)

from flask import Flask, render_template, jsonify, request
import json

app = Flask(__name__)

# 模拟漫画数据
COMICS = [
    {"id": 1, "title": "海贼王", "category": "冒险", "cover": "cover1.jpg"},
    {"id": 2, "title": "火影忍者", "category": "热血", "cover": "cover2.jpg"},
    {"id": 3, "title": "进击的巨人", "category": "奇幻", "cover": "cover3.jpg"},
    # ...更多数据
]

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/api/comics')
def get_comics():
    # 获取搜索参数
    search = request.args.get('search', '').lower()
    category = request.args.get('category', '')
    
    # 过滤数据
    filtered = COMICS
    if search:
        filtered = [c for c in filtered if search in c['title'].lower()]
    if category:
        filtered = [c for c in filtered if c['category'] == category]
    
    return jsonify(filtered)

if __name__ == '__main__':
    app.run(debug=True)

2. 前端HTML模板 (templates/index.html)

<!DOCTYPE html>
<html>
<head>
    <title>漫画阅读站</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js"></script>
    <script src="https://unpkg.com/imagesloaded@5/imagesloaded.pkgd.min.js"></script>
    <style>
        .grid { margin: 20px auto; max-width: 1200px; }
        .grid-item { width: 300px; margin: 10px; border: 1px solid #ddd; padding: 10px; }
        .grid-item img { width: 100%; height: auto; }
        .controls { text-align: center; margin: 20px; }
        input, select { padding: 8px; margin: 5px; }
        @media (max-width: 768px) {
            .grid-item { width: calc(50% - 20px); }
        }
        @media (max-width: 480px) {
            .grid-item { width: calc(100% - 20px); }
        }
    </style>
</head>
<body>
    <div class="controls">
        <input type="text" id="search" placeholder="搜索漫画...">
        <select id="category">
            <option value="">全部分类</option>
            <option value="冒险">冒险</option>
            <option value="热血">热血</option>
            <option value="奇幻">奇幻</option>
        </select>
    </div>
    
    <div class="grid" id="comic-grid">
        <!-- 漫画卡片通过JS动态加载 -->
    </div>

    <script>
        let grid;
        
        // 初始化瀑布流
        function initMasonry() {
            grid = new Masonry('#comic-grid', {
                itemSelector: '.grid-item',
                columnWidth: 300,
                gutter: 20,
                fitWidth: true
            });
        }
        
        // 加载漫画数据
        async function loadComics() {
            const search = document.getElementById('search').value;
            const category = document.getElementById('category').value;
            
            const params = new URLSearchParams();
            if (search) params.append('search', search);
            if (category) params.append('category', category);
            
            const response = await fetch(`/api/comics?${params}`);
            const comics = await response.json();
            
            renderComics(comics);
        }
        
        // 渲染漫画卡片
        function renderComics(comics) {
            const gridEl = document.getElementById('comic-grid');
            gridEl.innerHTML = '';
            
            comics.forEach(comic => {
                const item = document.createElement('div');
                item.className = 'grid-item';
                item.innerHTML = `
                    <img src="/static/${comic.cover}" alt="${comic.title}">
                    <h3>${comic.title}</h3>
                    <span class="category">${comic.category}</span>
                `;
                gridEl.appendChild(item);
            });
            
            // 等图片加载完重新布局
            imagesLoaded(gridEl, () => {
                if (grid) grid.destroy();
                initMasonry();
            });
        }
        
        // 实时搜索(防抖处理)
        let searchTimeout;
        function debouncedSearch() {
            clearTimeout(searchTimeout);
            searchTimeout = setTimeout(loadComics, 300);
        }
        
        // 事件监听
        document.getElementById('search').addEventListener('input', debouncedSearch);
        document.getElementById('category').addEventListener('change', loadComics);
        
        // 初始加载
        loadComics();
    </script>
</body>
</html>

实现要点:

  1. 瀑布流:用Masonry.js实现自适应网格布局,imagesLoaded确保图片加载完再计算布局
  2. 响应式:CSS媒体查询实现移动端适配(手机2列/1列)
  3. 实时搜索:JavaScript监听输入框变化,防抖处理避免频繁请求
  4. 数据交互:Flask提供RESTful API返回JSON数据

总结:前端处理展示逻辑,后端专注数据API。

一句话建议:用Masonry.js做瀑布流,AJAX实现无刷新过滤。

大佬学习效率都这么高吗

向大佬低头

"Don’t forget to create db file firstly."

db 该如何创建。。。


接触过 Laravel ,感觉差不多,所以比较快。😂

没有分页

先 fork 为敬

从开始学到完成花了一天多。。。这个学习效率。

开始学习到完成 1 天多。

大佬

数据怎么来的? 调用腾讯动漫么?

求项目思路。。

应该是没有文件夹,你在 ishuhui 下面建一个 tmp 文件夹,数据库在 tmp 文件夹里,好像文件夹不会自己创建,我也不知道为啥。。。

不是,鼠绘漫画的,代码里面可以看到 sap

看到 api。。。

数据量不多,所以没有分页。。。

正好海贼好几话没看了 先看了再说 哈哈~

风格不错

效率这么高的吗,向大佬低头

效率真高

感谢回复。现在按照 readme 本地调试成功,大家可以上车了~

ps: 偶然点到《钻石王牌》,好像都没有图片

你拉一下最新的代码,是因为鼠绘做了盗链处理。最新的在所有页面加了 <meta name=“referrer” content=“never”> 头。

如果还是没有,就是鼠绘那边没有了

学习,最近正在学 flask!

看了下,是鼠绘那边没了

膜拜大佬,牛 B 格拉斯

正在学 flask,我辈楷模

话说…图片地址直接用鼠绘的,有取得鼠绘那边的同意吗?

没有。。。仅学习使用,没有商业用途,没事吧应该?

大佬部署在哪裡?

老铁,我有看你的 laravel,我说这名字有点眼熟。

亚马逊免费服务器,一年免费

UI 好看

请问就是 ec2 吗? amazon 好多服务,乍一看头晕

就是 EC2

多谢,全是框架的风格,我就几乎没写😂😂

几年前写过一个抓取图片的站点,然后顺手就套了一个浏览图片的功能,刚好也能拿来看漫画
参考我发的贴 www.v2ex.com/t/206500

然而你公开了…这就不算学习了哦…
还是给鼠绘的管理员发封邮件说明下吧,如果对方同意的话那皆大欢喜,不同意的话该撤还是得撤

回到顶部