تصف معادلات Fresnel انعكاس الضوء عند الحادث على واجهة بين الوسائط البصرية المختلفة.
- https://en.wikipedia.org/wiki/fresnel_equations
# React 18+
yarn add @artsy/fresnel
# React 17
yarn add @artsy/fresnel@6جدول المحتويات
عند كتابة المكونات المستجيبة ، من الشائع استخدام استعلامات الوسائط لضبط الشاشة عند استيفاء شروط معينة. تاريخيا ، حدث هذا مباشرة في CSS/HTML:
@media screen and ( max-width : 767 px ) {
. my-container {
width : 100 % ;
}
}
@media screen and ( min-width : 768 px ) {
. my-container {
width : 50 % ;
}
} < div class =" my-container " /> من خلال التثبيت في تعريف نقطة التوقف ، يأخذ @artsy/fresnel هذا النهج التصريحي ويأتي إلى عالم React.
import React from "react"
import ReactDOM from "react-dom"
import { createMedia } from "@artsy/fresnel"
const { MediaContextProvider , Media } = createMedia ( {
// breakpoints values can be either strings or integers
breakpoints : {
sm : 0 ,
md : 768 ,
lg : 1024 ,
xl : 1192 ,
} ,
} )
const App = ( ) => (
< MediaContextProvider >
< Media at = "sm" >
< MobileApp />
</ Media >
< Media at = "md" >
< TabletApp />
</ Media >
< Media greaterThanOrEqual = "lg" >
< DesktopApp />
</ Media >
</ MediaContextProvider >
)
ReactDOM . render ( < App /> , document . getElementById ( "react" ) ) أول شيء مهم يجب ملاحظته هو أنه عند تقديم الخادم باستخدام @artsy/fresnel ، يتم تقديم جميع نقاط التوقف بواسطة الخادم. يتم لف كل مكون Media بواسطة CSS العادي الذي سيظهر فقط نقطة التوقف إذا كان يطابق حجم المتصفح الحالي للمستخدم. هذا يعني أن العميل يمكنه البدء بدقة في تقديم HTML/CSS أثناء استلامه للعلامة ، وهو ما يبعد وقتًا طويلاً قبل تشغيل تطبيق React. هذا يحسن الأداء المدرك للمستخدمين النهائيين.
لماذا لا تقدم فقط الجهاز الحالي؟ لا يمكننا تحديد نقطة التوقف التي يحتاجها جهازك بدقة على الخادم. يمكننا استخدام مكتبة لاستنشاق عامل مستخدم المستعرض ، لكن هذه ليست دقيقة دائمًا ، ولن تقدم لنا جميع المعلومات التي نحتاج إلى معرفتها عندما نؤدي تقديم الخادم. بمجرد أن يتم ربط أحذية JS من جانب العميل ، يتم غسلها ببساطة فوق DOM وتزيل الترميز غير ضروري ، عبر مكالمة matchMedia .
أولاً ، تكوين @artsy/fresnel في ملف Media يمكن مشاركته عبر التطبيق:
// Media.tsx
import { createMedia } from "@artsy/fresnel"
const ExampleAppMedia = createMedia ( {
breakpoints : {
sm : 0 ,
md : 768 ,
lg : 1024 ,
xl : 1192 ,
} ,
} )
// Generate CSS to be injected into the head
export const mediaStyle = ExampleAppMedia . createMediaStyle ( )
export const { Media , MediaContextProvider } = ExampleAppMedia قم بإنشاء ملف App جديد سيكون نقطة التشغيل لتطبيقنا:
// App.tsx
import React from "react"
import { Media , MediaContextProvider } from "./Media"
export const App = ( ) => {
return (
< MediaContextProvider >
< Media at = "sm" > Hello mobile! </ Media >
< Media greaterThan = "sm" > Hello desktop! </ Media >
</ MediaContextProvider >
)
} mount <App /> على العميل:
// client.tsx
import React from "react"
import ReactDOM from "react-dom"
import { App } from "./App"
ReactDOM . render ( < App /> , document . getElementById ( "react" ) ) ثم على الخادم ، قم بإعداد عرض SSR ونقل mediaStyle إلى علامة <style> في الرأس:
// server.tsx
import React from "react"
import ReactDOMServer from "react-dom/server"
import express from "express"
import { App } from "./App"
import { mediaStyle } from "./Media"
const app = express ( )
app . get ( "/" , ( _req , res ) => {
const html = ReactDOMServer . renderToString ( < App /> )
res . send ( `
<html>
<head>
<title>@artsy/fresnel - SSR Example</title>
<!–– Inject the generated styles into the page head -->
<style type="text/css"> ${ mediaStyle } </style>
</head>
<body>
<div id="react"> ${ html } </div>
<script src='/assets/app.js'></script>
</body>
</html>
` )
} )
app . listen ( 3000 , ( ) => {
console . warn ( "nApp started at http://localhost:3000 n" )
} )وهذا كل شيء! لاختبار وتعطيل JS وتوسيع نطاق نافذة المتصفح إلى حجم الهاتف المحمول وإعادة التحميل ؛ سيعزف ذلك بشكل صحيح تخطيط الهاتف المحمول دون الحاجة إلى استخدام وكيل المستخدم أو "تلميحات" من جانب الخادم.
@artsy/fresnel يعمل بشكل رائع مع نهج Gatsby أو Next.js الثابت في التقديم. انظر الأمثلة أدناه للحصول على تطبيق بسيط.
هناك أربعة أمثلة يمكن للمرء استكشافها في مجلد /examples :
في حين أن أمثلة SSR Basic ستحصل على واحدة بعيدًا ، يمكن لـ @artsy/fresnel أن تفعل الكثير. للحصول على غوص عميق شامل في ميزاته ، تحقق من تطبيق Sink Kitchen.
إذا كنت تستخدم Gatsby ، فيمكنك أيضًا تجربة Gatsby-Plugin-Fresnel لسهولة التكوين.
تتخذ الحلول الموجودة الأخرى مقاربة تم تقديمها مشروطًا ، مثل react-responsive أو react-media ، فأين يختلف هذا النهج؟
عرض جانب الخادم!
لكن أولاً ، ما هو العرض الشرطي؟
في النظام الإيكولوجي React ، فإن النهج الشائع لكتابة المكونات الإرهبية المستجيبة هو استخدام API matchMedia للمتصفح:
< Responsive >
{ ( { sm } ) => {
if ( sm ) {
return < MobileApp />
} else {
return < DesktopApp />
}
} }
</ Responsive >على العميل ، عندما تتم مطابقة نقطة توقف معينة ، فإن رد الفعل مشروطًا يجعل شجرة.
ومع ذلك ، فإن هذا النهج له بعض القيود على ما أردنا تحقيقه من خلال إعداد عرض من جانب الخادم:
من المستحيل معرفة نقطة التوقف الحالية للمستخدم خلال مرحلة عرض الخادم لأن ذلك يتطلب متصفحًا.
يعد إعداد أحجام نقطة الإيقاف بناءً على استنشاق وكيل المستخدم عرضة للأخطاء بسبب عدم القدرة على مطابقة إمكانيات الجهاز بدقة مع الحجم. قد يكون لدى أحد الأجهزة المحمولة كثافة بكسل أكبر من الآخر ، وقد يتناسب الجهاز المحمول إلى نقاط توقف متعددة عند أخذ اتجاه الجهاز في الاعتبار ، وعملاء سطح المكتب لا توجد طريقة لمعرفة ذلك على الإطلاق. أفضل Devs هو تخمين نقطة التوقف الحالية وملء <Responsive> مع الحالة المفترضة.
استقر Artsy على ما نعتقد أنه يجعل أفضل المقايضات. نحن نتعامل مع هذه المشكلة بالطريقة التالية:
قم بتوفير الترميز لجميع نقاط التوقف على الخادم وأرسله إلى أسفل السلك.
يتلقى المتصفح ترميزًا بتصميم الاستعلام المناسب للوسائط وسيبدأ على الفور في تقديم النتيجة المرئية المتوقعة لأي عرض للمنفذ الذي يوجد فيه المتصفح.
عندما يتم تحميل جميع JS ويبدأ React في مرحلة الإماهة ، فإننا نستفسر عن المتصفح عن نقطة الإيقاف الموجودة حاليًا ثم تحد من المكونات المقدمة إلى استعلامات الوسائط المطابقة. هذا يمنع أساليب دورة الحياة من إطلاق النار في المكونات الخفية و HTML غير المستخدمة التي يتم إعادة كتابتها إلى DOM.
بالإضافة إلى ذلك ، نقوم بتسجيل مستمعي الأحداث مع المتصفح لإخطار MediaContextProvider عندما تتم مطابقة نقطة توقف مختلفة ثم إعادة تقديم الشجرة باستخدام القيمة الجديدة للدعامة onlyMatch .
دعنا نقارن شكل شجرة مكون باستخدام matchMedia مع نهجنا:
| قبل | بعد |
|---|---|
< Responsive >
{ ( { sm } ) => {
if ( sm ) return < SmallArticleItem { ... props } />
else return < LargeArticleItem { ... props } />
} }
</ Responsive > | < >
< Media at = "sm" >
< SmallArticleItem { ... props } />
</ Media >
< Media greaterThan = "sm" >
< LargeArticleItem { ... props } />
</ Media >
</ > |
راجع تطبيق تقديم جانب الخادم للحصول على مثال عمل.
أول الأشياء أولاً. ستحتاج إلى تحديد نقاط التوقف والتفاعل اللازم لتصميمك لإنتاج مجموعة من مكونات الوسائط التي يمكنك استخدامها خلال التطبيق الخاص بك.
على سبيل المثال ، فكر في تطبيق يحتوي على نقاط التوقف التالية:
sm .md .lg .xl .والتفاعلات التالية:
hover .notHover .يمكنك بعد ذلك إنتاج مجموعة من مكونات الوسائط مثل:
// Media.tsx
const ExampleAppMedia = createMedia ( {
breakpoints : {
sm : 0 ,
md : 768 ,
lg : 1024 ,
xl : 1192 ,
} ,
interactions : {
hover : "(hover: hover)" ,
notHover : "(hover: none)" ,
landscape : "not all and (orientation: landscape)" ,
portrait : "not all and (orientation: portrait)" ,
} ,
} )
export const { Media , MediaContextProvider , createMediaStyle } = ExampleAppMediaكما ترون ، يتم تعريف نقاط التوقف عن طريق إزاحة البداية ، حيث من المتوقع أن يبدأ الأول من 0.
يؤثر مكون MediaContextProvider على كيفية تقديم مكونات Media . قم بتركيبه على جذر شجرة المكون الخاصة بك:
import React from "react"
import { MediaContextProvider } from "./Media"
export const App = ( ) => {
return < MediaContextProvider > ... </ MediaContextProvider >
} يحتوي مكون Media الذي تم إنشاؤه لتطبيقك على عدد قليل من الدعائم الحصرية المتبادلة التي تشكل واجهة برمجة التطبيقات التي ستستخدمها لإعلان تخطيطاتك المستجيبة. تعمل جميع هذه الدعائم بناءً على نقاط التوقف المسماة التي تم توفيرها عند إنشاء مكونات الوسائط.
import React from "react"
import { Media } from "./Media"
export const HomePage = ( ) => {
return (
< >
< Media at = "sm" > Hello mobile! </ Media >
< Media greaterThan = "sm" > Hello desktop! </ Media >
</ >
)
}الأمثلة المعطاة لكل دعامة تستخدم تعريفات نقطة الإيقاف كما هو محدد في قسم "الإعداد" أعلاه.
إذا كنت ترغب في تجنب div الأساسي الذي يتم إنشاؤه بواسطة <Media> وبدلاً من ذلك ، استخدم العنصر الخاص بك ، استخدم نموذج Render-Props ولكن تأكد من عدم تقديم أي أطفال عندما لا يكون ذلك ضروريًا:
export const HomePage = ( ) => {
return (
< >
< Media at = "sm" > Hello mobile! </ Media >
< Media greaterThan = "sm" >
{ ( className , renderChildren ) => {
return (
< MySpecialComponent className = { className } >
{ renderChildren ? "Hello desktop!" : null }
</ MySpecialComponent >
)
} }
</ Media >
</ >
)
} ملاحظة: يتم استخدام هذا فقط عند عرض SSR
إلى جانب مكونات Media و MediaContextProvider ، هناك وظيفة createMediaStyle تنتج تصميم CSS لجميع استعلامات الوسائط الممكنة التي يمكن أن تستفيد منها مثيل Media أثناء تمرير الترميز من الخادم إلى العميل أثناء الترطيب. إذا تم استخدام مجموعة فرعية فقط من مفاتيح نقطة التوقف ، فيمكن تحديد ذلك اختياريًا كمعلمة لتقليل الإخراج. تأكد من إدراج هذا في علامة <style> في المستند الخاص بك <head> .
من المستحسن القيام بهذا الإعداد في الوحدة النمطية الخاصة به بحيث يمكن استيراده بسهولة خلال تطبيقك:
import { createMedia } from "@artsy/fresnel"
const ExampleAppMedia = createMedia ( {
breakpoints : {
sm : 0 ,
md : 768 ,
lg : 1024 ,
xl : 1192 ,
} ,
} )
// Generate CSS to be injected into the head
export const mediaStyle = ExampleAppMedia . createMediaStyle ( ) // optional: .createMediaStyle(['at'])
export const { Media , MediaContextProvider } = ExampleAppMedia يمكن أن يقيد التقديم على نقاط توقف/تفاعلات محددة من خلال تحديد قائمة باستعلامات الوسائط لمطابقة. بشكل افتراضي ، سيتم تقديم كل شيء .
بشكل افتراضي ، عند تقديمه من جانب العميل ، سيتم استخدام واجهة برمجة matchMedia الخاصة بالمتصفح لزيادة تقييد قائمة onlyMatch على استعلامات الوسائط المتطابقة حاليًا. يتم ذلك لتجنب تشغيل خطافات دورة الحياة ذات الصلة بالمكونات المخفية.
تعطيل هذا السلوك مخصص في الغالب لأغراض تصحيح الأخطاء.
استخدم هذا لإعلان أن الأطفال يجب أن يكونوا مرئيين فقط عند نقطة انقطاع محددة ، مما يعني أن عرض منفذ العرض أكبر من أو يساوي إزاحة نقطة الإيقاف ، ولكن أقل من نقطة التوقف التالية ، إذا كان هناك واحد.
على سبيل المثال ، لن يكون أطفال إعلان Media مرئية إلا إذا كان عرض إطارات العرض بين 0 و 768 (768 غير مدرج) نقاط:
< Media at = "sm" > ... </ Media >قاعدة CSS المقابلة:
@media not all and ( min-width : 0 px ) and ( max-width : 767 px ) {
. fresnel-at-sm {
display : none !important ;
}
}استخدم هذا لإعلان أن الأطفال يجب أن يكونوا مرئيين فقط بينما يكون عرض منفذ العرض أقل من إزاحة بداية نقطة الإيقاف المحددة.
على سبيل المثال ، لن يكون أطفال إعلان Media مرئية إلا إذا كان عرض إطارات العرض بين 0 و 1024 (1024 غير مدرج) نقاط:
< Media lessThan = "lg" > ... </ Media >قاعدة CSS المقابلة:
@media not all and ( max-width : 1023 px ) {
. fresnel-lessThan-lg {
display : none !important ;
}
}استخدم هذا لإعلان أن الأطفال يجب أن يكونوا مرئيين فقط بينما يكون عرض منفذ العرض متساويًا أو أكبر من إزاحة بداية نقطة الإيقاف التالية .
على سبيل المثال ، لن يكون أطفال إعلان Media مرئية إلا إذا كان عرض إيماءة العرض متساويًا أو أكبر من 1024 نقطة:
< Media greaterThan = "md" > ... </ Media >قاعدة CSS المقابلة:
@media not all and ( min-width : 1024 px ) {
. fresnel-greaterThan-md {
display : none !important ;
}
}استخدم هذا لإعلان أن الأطفال يجب أن يكونوا مرئيين فقط في حين أن عرض منفذ العرض يساوي إزاحة بداية نقطة الإيقاف المحددة أو أكبر.
على سبيل المثال ، لن يكون أطفال إعلان Media مرئيًا إلا إذا كان عرض إيماءة العرض 768 نقطة أو أعلى:
< Media greaterThanOrEqual = "md" > ... </ Media >قاعدة CSS المقابلة:
@media not all and ( min-width : 768 px ) {
. fresnel-greaterThanOrEqual-md {
display : none !important ;
}
}استخدم هذا لإعلان أن الأطفال يجب أن يكونوا مرئيين فقط في حين أن عرض منفذ العرض يساوي إزاحة البداية لنقطة الإيقاف الأولى المحددة ولكن أقل من إزاحة بداية نقطة الإيقاف الثانية المحددة.
على سبيل المثال ، لن يكون أطفال إعلان Media مرئيًا إلا إذا كان عرض إيماءة العرض ما بين 768 و 1192 (1192 غير مدرج) نقاط:
< Media between = { [ "md" , "xl" ] } > ... </ Media >قاعدة CSS المقابلة:
@media not all and ( min-width : 768 px ) and ( max-width : 1191 px ) {
. fresnel-between-md-xl {
display : none !important ;
}
}الايجابيات:
سلبيات:
<Media> الذي يجدون أنفسهم فيه. هذه النقطة الأخيرة تعرض مشكلة مثيرة للاهتمام. كيف يمكن أن نمثل مكونًا يتم تصميمه بشكل مختلف عند نقاط توقف مختلفة؟ (دعونا نتخيل مثال matchMedia .)
< Sans size = { sm ? 2 : 3 } > < >
< Media at = "sm" > { this . getComponent ( "sm" ) } </ Media >
< Media greaterThan = "sm" > { this . getComponent ( ) } </ Media >
</ > getComponent ( breakpoint ?: string ) {
const sm = breakpoint === 'sm'
return < Sans size = { sm ? 2 : 3 } />
}ما زلنا نكتشف أنماطًا لهذا الغرض ، لذا يرجى إعلامنا إذا كان لديك اقتراحات.
يستخدم هذا المشروع إصدارًا تلقائيًا لإصداره تلقائيًا على كل العلاقات العامة. يجب أن يكون لكل PR ملصق يطابق أحد ما يلي
سيؤدي Major ، Minor ، و Patch إلى إنشاء إصدار جديد. استخدم التخصص في كسر التغييرات ، وصقل للميزات الجديدة غير المحفوظة ، والتصحيح لإصلاحات الأخطاء. لن يتسبب Trivial في الإصدار ويجب استخدامه عند تحديث الوثائق أو رمز غير المشروع.
إذا كنت لا ترغب في الإصدار على علاقات عامة معينة ولكن التغييرات ليست تافهة ، فاستخدم علامة Skip Release بجانب علامة الإصدار المناسبة.