برنامه نویسی

چگونه با Tailwind CSS و Next.js یک کامپوننت ویدئویی مدال بسازیم

نسخه نمایشی زنده / دانلود

به قسمت دوم سری خوش آمدید چگونه با Tailwind CSS یک کامپوننت مدال ویدیو بسازیم! در قسمت قبل یاد گرفتیم که چگونه با استفاده از یک کامپوننت ویدئویی مودال بسازیم Tailwind CSS و Alpine.js. در این مقاله، سطح بازی را بالا می‌بریم و به شما نشان می‌دهیم که چگونه a جزء قابل استفاده مجدد برای Next.js استفاده كردن TypeScript.

قبل از غواصی، توجه به این نکته مهم است که نحوه شروع کار با Next.js را پوشش نمی دهیم. توصیه می کنیم برای آن به اسناد رسمی مراجعه کنید. در عوض، ما بر روی ساخت کامپوننت با استفاده از آن تمرکز خواهیم کرد Next.js 13 با روتر برنامه (برنامه) و اجزای سرور React.

اگر مایلید ببینید که چگونه قبلاً برخی از اجزای ویدیویی مودال را در Next.js ساختیم، پیشنهاد می‌کنیم بررسی کنید مرتب، یک قالب زیبای وب سایت HTML و اعمال کنید، یک قالب طراحی وب موبایل!

بیایید با ترسیم کامپوننت خود با ایجاد یک فایل به نام شروع کنیم modal-video.tsx و تعریف تابع برای صادرات.

'use client'

export default function ModalVideo() {
    return (
    <div>

        {/* 1. The button */}
        {/* 2. The backdrop layer */}
        {/* 3. The modal video */}

    </div>
    )
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برای ساختار، ما از همان ساختار HTML استفاده می کنیم که قبلا با Tailwind CSS ایجاد کردیم، به یاد داشته باشید که جایگزین class صفات با className برای اطمینان از سازگاری با React.

علاوه بر این، ما از 'use client' دستورالعمل در بالای فایل و قبل از هر گونه واردات، زیرا می دانیم که این مؤلفه نیاز به تعامل سمت مشتری دارد.

حالت اولیه مودال را با useState تعریف کنید

حال بیایید حالت اولیه مودال را تعریف کنیم که باز باشد یا بسته. ما می توانیم با استفاده از useState از React قلاب کنید و حالت اولیه را روی آن قرار دهید false.

با تنظیم حالت اولیه مودال، از بسته بودن آن به صورت پیش فرض اطمینان حاصل می کنیم. بعداً، ما تابعی ایجاد خواهیم کرد که هنگام تعامل کاربر با آن، وضعیت مدال را به‌روزرسانی می‌کند. به این ترتیب، مدال تنها زمانی ظاهر می شود که کاربر روی تصویر کوچک کلیک کند.

'use client'

export default function ModalVideo() {
    const [modalOpen, setModalOpen] = useState<boolean>(false)

    return (
    <div>

        {/* 1. The button */}
        {/* 2. The backdrop layer */}
        {/* 3. The modal video */}

    </div>
    )
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

تغییر حالت مودال

حالا که حالت اولیه مودال را تعریف کردیم، بیایید به تغییر حالت آن بپردازیم. برای رسیدن به این هدف، علامت گذاری دکمه را به کامپوننت خود اضافه کنید و عبارت را اضافه کنید onClick رویدادی که تغییر می دهد modalOpen متغیر حالت به true.

با انجام این کار، وقتی کاربر روی تصویر کوچک کلیک می‌کند، مودال را فعال می‌کنیم.

'use client'

import Image from 'next/image'
import VideoThumb from '@/public/modal-video-thumb.jpg'

export default function ModalVideo() {
    const [modalOpen, setModalOpen] = useState<boolean>(false)

    return (
    <div>

        {/* 1. The button */}
        <button
        className="relative flex justify-center items-center focus:outline-none focus-visible:ring focus-visible:ring-indigo-300 rounded-3xl group"
        onClick={() => { setModalOpen(true) }}
        aria-label="Watch the video"
        >
        <Image className="rounded-3xl shadow-2xl transition-shadow duration-300 ease-in-out" src={VideoThumb} width={768} height={432} priority alt="Modal video thumbnail" />
        {/* Play icon */}
        <svg className="absolute pointer-events-none group-hover:scale-110 transition-transform duration-300 ease-in-out" xmlns="http://www.w3.org/2000/svg" width="72" height="72">
            <circle className="fill-white" cx="36" cy="36" r="36" fillOpacity=".8" />
            <path className="fill-indigo-500 drop-shadow-2xl" d="M44 36a.999.999 0 0 0-.427-.82l-10-7A1 1 0 0 0 32 29V43a.999.999 0 0 0 1.573.82l10-7A.995.995 0 0 0 44 36V36c0 .001 0 .001 0 0Z" />
        </svg>
        </button>

        {/* 2. The backdrop layer */}
        {/* 3. The modal video */}

    </div>
    )
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

رسیدگی به دید مودال و افزودن انتقال ورود/خروج

اکنون که می‌توانیم حالت مودال را تغییر دهیم، باید مطمئن شویم که قابلیت مشاهده لایه پس‌زمینه و ویدیوی مدال به modalOpen متغیر حالت علاوه بر این، می‌خواهیم هر بار که مدال باز یا بسته می‌شود، ترانزیشن‌های ورود و خروج را اضافه کنیم.

یکی از گزینه‌ها استفاده از کتابخانه React Transition Group برای دستیابی به این هدف است، اما باید دسترسی را نیز مدیریت کنیم، و مؤلفه را با توابعی که وقتی لایه پس‌زمینه کلیک می‌شود یا کلید فرار فشار داده می‌شود، مودال را می‌بندند.

از طرف دیگر، می‌توانیم از یک مؤلفه UI از پیش ساخته شده استفاده کنیم که همه اینها را برای ما مدیریت می‌کند: Headless UI. Headless UI یک کتابخانه از اجزای رابط کاربری کاملاً قابل دسترسی است که توسط سازندگان Tailwind CSS ساخته شده است. این شامل مجموعه ای از اجزای آماده برای استفاده، از جمله منو، پاپاور، تب ها، گفتگو و موارد دیگر است.

برای شروع کار با Headless UI، ابتدا آن را با استفاده از دستور ساده ترمینال نصب کنید npm install @headlessui/react@latest. پس از نصب، وارد کنید Dialog و Transition اجزای سازنده و تعریف ساختار لایه پس زمینه و ویدیوی مدال، همانطور که در مثال زیر نشان داده شده است:

'use client'

import { useState, Fragment } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import Image from 'next/image'

export default function ModalVideo() {
    const [modalOpen, setModalOpen] = useState<boolean>(false)

    return (
    <div>

        {/* 1. The button */}
        <button
        className="relative flex justify-center items-center focus:outline-none focus-visible:ring focus-visible:ring-indigo-300 rounded-3xl group"
        onClick={() => { setModalOpen(true) }}
        aria-label="Watch the video"
        >
        <Image className="rounded-3xl shadow-2xl transition-shadow duration-300 ease-in-out" src={thumb} width={thumbWidth} height={thumbHeight} priority alt="Modal video thumbnail" />
        {/* Play icon */}
        <svg className="absolute pointer-events-none group-hover:scale-110 transition-transform duration-300 ease-in-out" xmlns="http://www.w3.org/2000/svg" width="72" height="72">
            <circle className="fill-white" cx="36" cy="36" r="36" fillOpacity=".8" />
            <path className="fill-indigo-500 drop-shadow-2xl" d="M44 36a.999.999 0 0 0-.427-.82l-10-7A1 1 0 0 0 32 29V43a.999.999 0 0 0 1.573.82l10-7A.995.995 0 0 0 44 36V36c0 .001 0 .001 0 0Z" />
        </svg>
        </button>

        <Transition show={modalOpen} as={Fragment}>
        <Dialog onClose={() => setModalOpen(false)}>

            {/* 2. The backdrop layer */}
            <Transition.Child
            className="fixed inset-0 z-[99999] bg-black bg-opacity-50 transition-opacity"
            enter="transition ease-out duration-200"
            enterFrom="opacity-0"
            enterTo="opacity-100"
            leave="transition ease-out duration-100"
            leaveFrom="opacity-100"
            leaveTo="opacity-0"
            aria-hidden="true"
            />

            {/* 3. The modal video */}
            <Transition.Child
            className="fixed inset-0 z-[99999] flex p-6"
            enter="transition ease-out duration-300"
            enterFrom="opacity-0 scale-75"
            enterTo="opacity-100 scale-100"
            leave="transition ease-out duration-200"
            leaveFrom="opacity-100 scale-100"
            leaveTo="opacity-0 scale-75"
            >
            <div className="max-w-5xl mx-auto h-full flex items-center">
                <Dialog.Panel className="w-full max-h-full rounded-3xl shadow-2xl aspect-video bg-black overflow-hidden">
                <video width="1920" height="1080" loop controls>
                    <source src="/video.mp4" type="video/mp4" />
                    Your browser does not support the video tag.
                </video>
                </Dialog.Panel>
            </div>
            </Transition.Child>

        </Dialog>
        </Transition>

    </div>
    )
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این Transition کامپوننت بخشی از کتابخانه Headless UI است که به شما امکان می دهد محتوا را بر اساس قابلیت مشاهده آن متحرک کنید.

وقتی که modalOpen متغیر state روی تنظیم شده است true، Transition کامپوننت محتوای پیچیده شده در داخل آن را نشان می دهد. این کار با عبور از modalOpen متغیر به show ویژگی.

این Transition جزء دارای دو است Transition.Child اجزایی که پس‌زمینه و ویدیوی مدال را به‌طور جداگانه می‌پیچند. هر یک Transition.Child کامپوننت مجموعه ای از ویژگی های خاص خود را دارد تا مشخص کند که چگونه باید هنگام باز و بسته شدن مدال ظاهر و ناپدید شود. این ویژگی ها در ویژگی های زیر تعریف می شوند:

  • enter: وقتی کامپوننت وارد می شود، ویژگی انتقال CSS را تعریف می کند.
  • enterFrom: حالت شروع انتقال را هنگام ورود مؤلفه مشخص می کند.
  • enterTo: حالت پایانی انتقال را هنگام ورود مؤلفه تعریف می کند.
  • leave: وقتی کامپوننت در حال خروج است، ویژگی انتقال CSS را تعریف می کند.
  • leaveFrom: حالت شروع انتقال را در هنگام خروج جزء تعریف می کند.
  • leaveTo: حالت پایانی انتقال را زمانی که جزء در حال خروج است را مشخص می کند.

بعد، ما داریم Dialog جزء که شامل یک onClose پاسخ به تماس که هنگامی که کاربر در خارج از صفحه کلیک می‌کند فعال می‌شود Dialog.Panel یا کلید فرار را فشار می دهد. ما از این تماس برای تنظیم استفاده خواهیم کرد modalOpen دولت به false.

در این مرحله، ویدیوی مودال ما کاملاً کاربردی است! مرحله آخر این است که اطمینان حاصل کنید که وقتی مودال باز می شود، ویدیو به طور خودکار پخش می شود. بیایید نگاهی بیندازیم که چگونه می توانیم به این امر دست یابیم.

پخش خودکار ویدیو با باز شدن مودال

برخلاف مثال Alpine.js که در مقاله قبلی نشان دادیم، در Next.js، زمانی که modal بسته می‌شود، محتوای مودال خارج می‌شود. به همین دلیل، لازم نیست نگران توقف موقت ویدیو در زمان بسته شدن مدال باشیم و تنها بر روی شروع ویدیو زمانی که باز می شود تمرکز می کنیم.

برای انجام این کار، باید به ویدیو رجوع کنیم و به آن بگوییم شروع به پخش کند. ما نمی توانیم از معمول استفاده کنیم useEffect React hook به این دلیل است که ویدیو در داخل کامپوننتی قرار دارد که با بسته شدن مودال جدا می شود ref برمی گشت null.

در عوض، ما از afterEnter پاسخ تماس در Transition جزء. این تماس پس از پایان باز شدن مدال اجرا می‌شود، بنابراین می‌توانیم از طریق به عنصر ویدیو دسترسی داشته باشیم ref و بهش بگو بازی کنه

'use client'

import { useState, useRef, Fragment } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import Image from 'next/image'

export default function ModalVideo() {
    const [modalOpen, setModalOpen] = useState<boolean>(false)

    return (
    <div>

        {/* 1. The button */}
        <button
        className="relative flex justify-center items-center focus:outline-none focus-visible:ring focus-visible:ring-indigo-300 rounded-3xl group"
        onClick={() => { setModalOpen(true) }}
        aria-label="Watch the video"
        >
        <Image className="rounded-3xl shadow-2xl transition-shadow duration-300 ease-in-out" src={thumb} width={thumbWidth} height={thumbHeight} priority alt="Modal video thumbnail" />
        {/* Play icon */}
        <svg className="absolute pointer-events-none group-hover:scale-110 transition-transform duration-300 ease-in-out" xmlns="http://www.w3.org/2000/svg" width="72" height="72">
            <circle className="fill-white" cx="36" cy="36" r="36" fillOpacity=".8" />
            <path className="fill-indigo-500 drop-shadow-2xl" d="M44 36a.999.999 0 0 0-.427-.82l-10-7A1 1 0 0 0 32 29V43a.999.999 0 0 0 1.573.82l10-7A.995.995 0 0 0 44 36V36c0 .001 0 .001 0 0Z" />
        </svg>
        </button>

        <Transition show={modalOpen} as={Fragment} afterEnter={() => videoRef.current?.play()}>
        <Dialog initialFocus={videoRef} onClose={() => setModalOpen(false)}>

            {/* 2. The backdrop layer */}
            <Transition.Child
            className="fixed inset-0 z-[99999] bg-black bg-opacity-50 transition-opacity"
            enter="transition ease-out duration-200"
            enterFrom="opacity-0"
            enterTo="opacity-100"
            leave="transition ease-out duration-100"
            leaveFrom="opacity-100"
            leaveTo="opacity-0"
            aria-hidden="true"
            />

            {/* 3. The modal video */}
            <Transition.Child
            className="fixed inset-0 z-[99999] flex p-6"
            enter="transition ease-out duration-300"
            enterFrom="opacity-0 scale-75"
            enterTo="opacity-100 scale-100"
            leave="transition ease-out duration-200"
            leaveFrom="opacity-100 scale-100"
            leaveTo="opacity-0 scale-75"
            >
            <div className="max-w-5xl mx-auto h-full flex items-center">
                <Dialog.Panel className="w-full max-h-full rounded-3xl shadow-2xl aspect-video bg-black overflow-hidden">
                <video ref={videoRef} width="1920" height="1080" loop controls>
                    <source src="/video.mp4" type="video/mp4" />
                    Your browser does not support the video tag.
                </video>
                </Dialog.Panel>
            </div>
            </Transition.Child>

        </Dialog>
        </Transition>

    </div>
    )
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در نهایت، برای اطمینان از اینکه عنصر ویدیویی هنگام باز شدن مدال متمرکز است، می‌توانیم آن را پاس کنیم videoRef به دیالوگ initialFocus ویژگی.

با آن، کامپوننت ویدیوی مودال اکنون کامل شده است و می‌توان آن را وارد کرد و در کامپوننت دیگری مانند زیر استفاده کرد:

<ModalVideo />
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اگر فقط یک بار نیاز دارید که از کامپوننت در برنامه خود استفاده کنید، پس خوب هستید! اما اگر می‌خواهید از آن با تصاویر کوچک و ویدیوهای مختلف استفاده مجدد کنید، ممکن است چند تنظیم دیگر لازم باشد.

قابلیت استفاده مجدد مولفه ویدئویی معین

برای اطمینان از اینکه ModalVideo کامپوننت قابل استفاده مجدد است، ما باید ویژگی های زیر را به عنوان ابزار به آن منتقل کنیم:

  • ویژگی منبع (src) تصویر کوچک
  • ابعاد تصویر بند انگشتی
  • متن جایگزین (alt) برای تصویر کوچک
  • ویژگی منبع (src) ویدیو
  • ابعاد فیلم
<ModalVideo
    thumb={VideoThumb}
    thumbWidth={768}
    thumbHeight={432}
    thumbAlt="Modal video thumbnail"
    video="/video.mp4"
    videoWidth={1920}
    videoHeight={1080} />
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سپس می‌توانیم این پروپوزال‌ها را با فهرست کردن نام‌هایشان در مولفه تابع بخوانیم. برای اطمینان از ساختار مناسب اجزاء، یک شی با رابط TypeScript نیز تعریف می کنیم:

interface ModalVideoProps {
    thumb: StaticImageData
    thumbWidth: number
    thumbHeight: number
    thumbAlt: string
    video: string
    videoWidth: number
    videoHeight: number
}

export default function ModalVideo({
    thumb,
    thumbWidth,
    thumbHeight,
    thumbAlt,
    video,
    videoWidth,
    videoHeight,
}: ModalVideoProps) {
...
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نتیجه گیری

و در اینجا نتیجه نهایی کامپوننت ویدیوی مودال ساخته شده با Next.js، TypeScript و Tailwind CSS است:

'use client'

import { useState, useRef, Fragment } from 'react'
import type { StaticImageData } from 'next/image'
import { Dialog, Transition } from '@headlessui/react'
import Image from 'next/image'

interface ModalVideoProps {
    thumb: StaticImageData
    thumbWidth: number
    thumbHeight: number
    thumbAlt: string
    video: string
    videoWidth: number
    videoHeight: number
}

export default function ModalVideo({
    thumb,
    thumbWidth,
    thumbHeight,
    thumbAlt,
    video,
    videoWidth,
    videoHeight,
}: ModalVideoProps) {
    const [modalOpen, setModalOpen] = useState<boolean>(false)
    const videoRef = useRef<HTMLVideoElement>(null)

    return (
    <div>

        {/* Video thumbnail */}
        <button
        className="relative flex justify-center items-center focus:outline-none focus-visible:ring focus-visible:ring-indigo-300 rounded-3xl group"
        onClick={() => { setModalOpen(true) }}
        aria-label="Watch the video"
        >
        <Image className="rounded-3xl shadow-2xl transition-shadow duration-300 ease-in-out" src={thumb} width={thumbWidth} height={thumbHeight} priority alt={thumbAlt} />
        {/* Play icon */}
        <svg className="absolute pointer-events-none group-hover:scale-110 transition-transform duration-300 ease-in-out" xmlns="http://www.w3.org/2000/svg" width="72" height="72">
            <circle className="fill-white" cx="36" cy="36" r="36" fillOpacity=".8" />
            <path className="fill-indigo-500 drop-shadow-2xl" d="M44 36a.999.999 0 0 0-.427-.82l-10-7A1 1 0 0 0 32 29V43a.999.999 0 0 0 1.573.82l10-7A.995.995 0 0 0 44 36V36c0 .001 0 .001 0 0Z" />
        </svg>
        </button>
        {/* End: Video thumbnail */}

        <Transition show={modalOpen} as={Fragment} afterEnter={() => videoRef.current?.play()}>
        <Dialog initialFocus={videoRef} onClose={() => setModalOpen(false)}>

            {/* Modal backdrop */}
            <Transition.Child
            className="fixed inset-0 z-10 bg-black bg-opacity-50 transition-opacity"
            enter="transition ease-out duration-200"
            enterFrom="opacity-0"
            enterTo="opacity-100"
            leave="transition ease-out duration-100"
            leaveFrom="opacity-100"
            leaveTo="opacity-0"
            aria-hidden="true"
            />
            {/* End: Modal backdrop */}

            {/* Modal dialog */}
            <Transition.Child
            className="fixed inset-0 z-10 flex p-6"
            enter="transition ease-out duration-300"
            enterFrom="opacity-0 scale-75"
            enterTo="opacity-100 scale-100"
            leave="transition ease-out duration-200"
            leaveFrom="opacity-100 scale-100"
            leaveTo="opacity-0 scale-75"
            >
            <div className="max-w-5xl mx-auto h-full flex items-center">
                <Dialog.Panel className="w-full max-h-full rounded-3xl shadow-2xl aspect-video bg-black overflow-hidden">
                <video ref={videoRef} width={videoWidth} height={videoHeight} loop controls>
                    <source src={video} type="video/mp4" />
                    Your browser does not support the video tag.
                </video>
                </Dialog.Panel>
            </div>
            </Transition.Child>
            {/* End: Modal dialog */}

        </Dialog>
        </Transition>

    </div>
    )
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نکته خوب در مورد این کامپوننت این است که با عبور دادن props برای تصویر کوچک و ویدیو، می توانید به راحتی از آن در قسمت های مختلف برنامه خود استفاده کنید.

اگر علاقه مند به یادگیری نحوه ساخت این کامپوننت در پشته های دیگر هستید، در اینجا پیوندهای قسمت اول و سوم این مجموعه وجود دارد:

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

همچنین ببینید
بستن
دکمه بازگشت به بالا