آموزش طراحی سایت و برنامهنویسی وب
اسکریپت فیلتر و جستجوی هوشمند با جستجوی بلادرنگ (سورس کد JS خالص)
- جستجو همزمان با تایپ انجام میشود، اما با debounce فقط وقتی کاربر ۲۰۰ میلیثانیه دست از تایپ بکشد، تا صفحه کند نشود.
- همهٔ فیلترها (متن، دسته، قیمت و مرتبسازی) در یک شیء
stateنگهداری و در یک تابع اعمال میشوند. - جستجو به «ی» و «ک» عربی و فاصلههای اضافه حساس نیست و کلمهٔ پیداشده در نتیجه هایلایت میشود.
- فیلترها در آدرس صفحه ذخیره میشوند تا کاربر بتواند نتیجه را با دیگران به اشتراک بگذارد.
وقتی تعداد محصولات، مقالات یا نمونهکارهای سایت زیاد میشود، کاربر باید بتواند در چند ثانیه چیزی را که میخواهد پیدا کند. فیلتر و جستجوی هوشمند دقیقاً همین کار را میکند: با هر حرف تایپشده یا هر فیلتر انتخابشده، نتیجه بدون رفرش صفحه بهروز میشود. در این مطلب از طاووس وب نسخهٔ کامل و بهروز این اسکریپت را با جاوااسکریپت خالص میسازیم.
دموی زنده جستجو و فیلتر
تایپ کنید، دسته انتخاب کنید یا قیمت را تغییر دهید
قیمتها به هزار تومان و محصولات نمونه هستند.
ایدهٔ اصلی: یک state، یک تابع
اشتباه رایج در اسکریپتهای فیلتر این است که هر فیلتر مستقیم کارتها را پنهان یا نمایش میدهد؛ نتیجه این میشود که فیلترها با هم تداخل پیدا میکنند. راه درست این است که همهٔ انتخابهای کاربر در یک شیء نگهداری شوند و یک تابع، هر بار کل داده را از اول با همهٔ شرطها فیلتر کند:

کد HTML
<div class="finder">
<input type="search" id="q" placeholder="جستجو…" aria-label="جستجو">
<select id="cat" aria-label="دستهبندی">
<option value="">همهٔ دستهها</option>
<option>کفش</option><option>کیف</option><option>ساعت</option>
</select>
<input type="range" id="max" min="100" max="3000" step="100" value="3000">
<select id="sort">
<option value="new">جدیدترین</option>
<option value="low">ارزانترین</option>
<option value="high">گرانترین</option>
</select>
<p id="count" aria-live="polite"></p>
<div id="results"></div>
</div>کد جاوااسکریپت فیلتر و جستجوی هوشمند
دادهها میتوانند از یک آرایهٔ ثابت، فایل JSON یا REST API وردپرس بیایند. اینجا یک آرایهٔ ساده داریم:
const products = [
{ title: 'کفش ورزشی رانینگ', cat: 'کفش', price: 1850, date: 9 },
{ title: 'کیف دوشی چرم', cat: 'کیف', price: 1500, date: 8 },
{ title: 'ساعت مچی کلاسیک', cat: 'ساعت', price: 2900, date: 6 },
// ...
];
const state = { q: '', cat: '', max: 3000, sort: 'new' };
// یکسانسازی متن فارسی: ی و ک عربی، نیمفاصله و فاصلههای اضافه
const normalize = (s) => s
.replace(/ي/g, 'ی').replace(/ك/g, 'ک')
.replace(/\u200c/g, ' ').replace(/\s+/g, ' ')
.trim().toLowerCase();
function applyFilters() {
const q = normalize(state.q);
const list = products
.filter(p => !q || normalize(p.title).includes(q))
.filter(p => !state.cat || p.cat === state.cat)
.filter(p => p.price <= state.max)
.sort((a, b) =>
state.sort === 'low' ? a.price - b.price :
state.sort === 'high' ? b.price - a.price :
b.date - a.date);
render(list);
}تابع render نتیجه را میسازد. برای امنیت، متنها با textContent یا escape وارد صفحه میشوند:
const results = document.getElementById('results');
const count = document.getElementById('count');
function render(list) {
results.replaceChildren(...list.map(p => {
const card = document.createElement('div');
card.className = 'card';
card.innerHTML = '<b></b><small></small><span></span>';
card.querySelector('b').textContent = p.title;
card.querySelector('small').textContent = p.cat;
card.querySelector('span').textContent = p.price.toLocaleString('fa-IR') + ' هزار تومان';
return card;
}));
count.textContent = list.length
? `${list.length.toLocaleString('fa-IR')} نتیجه`
: 'نتیجهای پیدا نشد؛ فیلترها را کمتر کنید.';
}و در آخر، اتصال ورودیها به state. جستجو با debounce اجرا میشود:
function debounce(fn, ms = 200) {
let t;
return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), ms); };
}
const $ = (id) => document.getElementById(id);
$('q').addEventListener('input', debounce((e) => {
state.q = e.target.value; applyFilters(); syncURL();
}));
$('cat').addEventListener('change', (e) => { state.cat = e.target.value; applyFilters(); syncURL(); });
$('max').addEventListener('input', (e) => { state.max = +e.target.value; applyFilters(); syncURL(); });
$('sort').addEventListener('change', (e) => { state.sort = e.target.value; applyFilters(); syncURL(); });
applyFilters();ذخیرهٔ فیلترها در آدرس صفحه
اگر کاربر نتیجهٔ فیلترشده را برای کسی بفرستد، باید همان نتیجه باز شود. با URLSearchParams فیلترها در آدرس ذخیره و هنگام بارگذاری خوانده میشوند:
function syncURL() {
const p = new URLSearchParams();
if (state.q) p.set('q', state.q);
if (state.cat) p.set('cat', state.cat);
if (state.max < 3000) p.set('max', state.max);
if (state.sort !== 'new') p.set('sort', state.sort);
history.replaceState(null, '', p.toString() ? '?' + p : location.pathname);
}
// هنگام بارگذاری صفحه
const init = new URLSearchParams(location.search);
state.q = init.get('q') || '';
state.cat = init.get('cat') || '';
state.max = +(init.get('max') || 3000);
state.sort = init.get('sort') || 'new';از replaceState بهجای pushState استفاده کردهایم تا با هر حرف تایپشده، یک صفحه به تاریخچهٔ مرورگر اضافه نشود و دکمهٔ بازگشت کاربر را اذیت نکند.
نکات کارایی و سئو
- تا چند صد آیتم، فیلتر سمت کاربر کافی است. برای هزاران محصول، جستجو باید سمت سرور (مثلاً با REST API وردپرس یا افزونههای جستجوی ووکامرس) انجام شود و فقط نتیجه برگردد.
- محتوای اصلی را با جاوااسکریپت نسازید. اگر لیست محصولات فقط با JS ساخته شود، ممکن است دیرتر یا ناقص ایندکس شود. بهتر است کارتها در HTML صفحه باشند و اسکریپت فقط آنها را پنهان و نمایش دهد.
- صفحههای فیلتر را ایندکس نکنید. ترکیبهای بیشمار فیلتر، هزاران آدرس تکراری میسازد؛ تگ canonical صفحه باید به آدرس بدون پارامتر اشاره کند.
- بازخورد بدهید. تعداد نتیجه و پیام «چیزی پیدا نشد» با پیشنهاد کم کردن فیلترها، کاربر را سردرگم نمیکند.
برای چیدمان کارتهای نتیجه، آموزش CSS Grid و برای ساخت سایتی که محصولاتش راحت پیدا شوند، مطلب سئو چیست را هم بخوانید.
مشتریهای فروشگاهتان محصول را پیدا میکنند؟طاووس وب جستجو، فیلتر و مسیر خرید فروشگاه اینترنتی را طوری طراحی میکند که کاربر سریعتر به محصول و پرداخت برسد.
طراحی سایت فروشگاهیسؤالات متداول فیلتر و جستجوی هوشمند
Debounce چیست و چرا لازم است؟
Debounce اجرای تابع را تا وقتی کاربر برای مدت کوتاهی تایپ را متوقف کند عقب میاندازد. بدون آن، با هر حرف یک جستجو اجرا میشود و در دادههای زیاد، صفحه کند میشود.
چطور این اسکریپت را به محصولات ووکامرس وصل کنم؟
میتوانید آرایهٔ products را از آدرس /wp-json/wc/store/v1/products بگیرید یا کارتهای محصول موجود در صفحه را با data-attribute علامتگذاری کنید و فقط پنهان و نمایششان دهید.
چرا جستجوی «کيف» با ی عربی نتیجه نمیدهد؟
در کد این مطلب، تابع normalize ی و ک عربی و نیمفاصله را یکسانسازی میکند تا این مشکل پیش نیاید.
برای چند محصول مناسب است؟
برای چند ده تا چند صد آیتم عالی است. برای هزاران محصول، جستجوی سمت سرور یا صفحهبندی لازم است.
آیا فیلترهای آدرس صفحه روی سئو اثر منفی دارند؟
اگر canonical صفحه به آدرس اصلی اشاره کند، خیر. آدرسهای دارای پارامتر نباید در نقشهٔ سایت قرار بگیرند.