Flask应用的标准目录结构中,静态文件通常放在static文件夹中:
/your-application
/static
/css
style.css
/js
app.js
/images
logo.png
/fonts
font.woff2
/templates
index.html
app.py
使用url_for函数生成静态文件的URL是最佳实践:
<!-- 引用CSS文件 -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<!-- 引用JavaScript文件 -->
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
<!-- 引用图片 -->
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="Logo">
<!-- 引用字体文件 -->
<style>
@font-face {
font-family: 'CustomFont';
src: url("{{ url_for('static', filename='fonts/font.woff2') }}") format('woff2');
}
</style>
{{ }}来转义双花括号,避免被Blade解析。
在Flask应用中,可以配置静态文件的处理方式:
from flask import Flask, render_template
app = Flask(__name__)
# 自定义静态文件夹路径
# app = Flask(__name__, static_folder='assets')
# 自定义静态URL路径
# app = Flask(__name__, static_url_path='/public')
@app.route('/')
def home():
return render_template('index.html')
if __name__ == '__main__':
app.run(debug=True)
以下是一个包含静态文件引用的完整HTML模板示例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flask应用示例</title>
<!-- Bootstrap CSS -->
<link href="@{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
<!-- 自定义CSS -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<!-- Font Awesome -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/font-awesome.min.css') }}">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="/">
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="Logo" height="40">
</a>
</nav>
<div class="container mt-4">
<h1>欢迎使用Flask应用</h1>
<p>这是一个示例页面,展示了静态文件的使用。</p>
<div class="row mt-4">
<div class="col-md-4">
<div class="card">
<img src="{{ url_for('static', filename='images/card1.jpg') }}" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">功能一</h5>
<p class="card-text">这是一个示例卡片。</p>
</div>
</div>
</div>
</div>
</div>
<!-- Bootstrap JS -->
<script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
<!-- 自定义JS -->
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
</body>
</html>
示例style.css文件内容:
/* static/css/style.css */
/* 自定义变量 */
:root {
--primary-color: #007bff;
--secondary-color: #6c757d;
--success-color: #28a745;
}
/* 全局样式 */
body {
font-family: 'Arial', sans-serif;
background-color: #f8f9fa;
color: #333;
}
/* 导航栏自定义 */
.navbar-brand img {
transition: transform 0.3s ease;
}
.navbar-brand img:hover {
transform: scale(1.05);
}
/* 卡片样式 */
.card {
border: 1px solid #dee2e6;
border-radius: 8px;
transition: box-shadow 0.3s ease;
margin-bottom: 20px;
}
.card:hover {
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.card-img-top {
height: 200px;
object-fit: cover;
}
/* 按钮样式 */
.btn-custom {
background-color: var(--primary-color);
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
transition: background-color 0.3s ease;
}
.btn-custom:hover {
background-color: #0056b3;
}
/* 响应式设计 */
@media(max-width: 768px) {
.container {
padding: 0 15px;
}
.card {
margin-bottom: 15px;
}
}
示例app.js文件内容:
// static/js/app.js
// 页面加载完成后执行
document.addEventListener('DOMContentLoaded', function() {
console.log('页面加载完成!');
// 示例:为所有卡片添加点击事件
const cards = document.querySelectorAll('.card');
cards.forEach(card => {
card.addEventListener('click', function() {
this.classList.toggle('border-primary');
console.log('卡片被点击');
});
});
// 示例:表单验证
const forms = document.querySelectorAll('form');
forms.forEach(form => {
form.addEventListener('submit', function(event) {
const inputs = this.querySelectorAll('input[required]');
let isValid = true;
inputs.forEach(input => {
if (!input.value.trim()) {
isValid = false;
input.classList.add('is-invalid');
} else {
input.classList.remove('is-invalid');
}
});
if (!isValid) {
event.preventDefault();
alert('请填写所有必填字段!');
}
});
});
// 示例:图片懒加载
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.add('loaded');
observer.unobserve(img);
}
});
});
images.forEach(img => imageObserver.observe(img));
});
// 工具函数
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `alert alert-${type} alert-dismissible fade show`;
notification.innerHTML = `
${message}
`;
document.body.appendChild(notification);
setTimeout(() => {
notification.remove();
}, 5000);
}
检查:
static目录中static_url_path配置# 在生产环境中配置静态文件缓存
if not app.debug:
@app.after_request
def add_header(response):
if response.headers.get('Content-Type', '').startswith('text/') or \
'javascript' in response.headers.get('Content-Type', '') or \
'css' in response.headers.get('Content-Type', ''):
response.headers['Cache-Control'] = 'public, max-age=31536000'
return response