Are you an LLM? You can read better optimized documentation at /front/component/ma-form/examples/mobile-responsive.md for this page in Markdown format
Mobile Responsiveness β
Demonstrates MaForm's responsive features on mobile devices, including breakpoint adaptation, mobile optimization, touch interaction, and device-specific configurations.
Features β
- Breakpoint Responsiveness: Automatically adapts layout based on screen size
- Mobile Optimization: Special optimizations for mobile devices
- Touch-Friendly: Interaction design optimized for touch operations
- Device Detection: Automatically identifies mobile devices and applies corresponding configurations
- Flexible Configuration: Supports mobile-specific field configurations
Responsive Breakpoint System β
1. Built-in Breakpoint Definitions β
typescript
const responsiveBreakpoints = {
xs: 576, // Extra small screens < 576px
sm: 768, // Small screens β₯ 768px
md: 992, // Medium screens β₯ 992px
lg: 1200, // Large screens β₯ 1200px
xl: 1920 // Extra large screens β₯ 1920px
}
// Mobile breakpoint configuration
const mobileFormOptions = {
mobileBreakpoint: 768, // Mobile breakpoint
responsiveConfig: {
enabled: true,
mobileSingleColumn: true, // Single-column layout on mobile
mobileHideLabels: false, // Whether to hide labels
breakpoints: responsiveBreakpoints
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2. Adaptive Grid Configuration β
typescript
const responsiveFormItems = [
{
label: 'Username',
prop: 'username',
render: 'input',
cols: {
// Basic grid configuration
span: 12,
// Responsive configuration
xs: 24, // Extra small screens: full width
sm: 24, // Small screens: full width
md: 12, // Medium screens: half width
lg: 8, // Large screens: one-third width
xl: 6 // Extra large screens: one-quarter width
}
},
{
label: 'Email Address',
prop: 'email',
render: 'input',
renderProps: {
type: 'email'
},
cols: {
xs: { span: 24, offset: 0 },
sm: { span: 24, offset: 0 },
md: { span: 12, offset: 0 },
lg: { span: 8, offset: 0 },
xl: { span: 6, offset: 0 }
}
},
{
label: 'Phone Number',
prop: 'phone',
render: 'input',
cols: {
xs: 24,
sm: 24,
md: 12,
lg: 8,
xl: 6
},
// Mobile-specific configuration
mobileProps: {
type: 'tel', // Use telephone keyboard on mobile
inputmode: 'numeric' // Numeric input mode
}
}
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Mobile-Specific Configuration β
1. mobileProps Configuration β
typescript
const mobileOptimizedFields = [
{
label: 'Mobile Number',
prop: 'mobile',
render: 'input',
renderProps: {
placeholder: 'Enter mobile number'
},
// Mobile-specific properties
mobileProps: {
type: 'tel',
inputmode: 'numeric',
pattern: '[0-9]*',
autocomplete: 'tel',
size: 'large' // Use large size on mobile
}
},
{
label: 'Email',
prop: 'email',
render: 'input',
renderProps: {
type: 'email',
placeholder: 'Enter email address'
},
mobileProps: {
inputmode: 'email',
autocomplete: 'email',
autocapitalize: 'none', // Disable auto-capitalization
spellcheck: false, // Disable spell check
size: 'large'
}
},
{
label: 'Website',
prop: 'website',
render: 'input',
renderProps: {
placeholder: 'Enter website URL'
},
mobileProps: {
type: 'url',
inputmode: 'url',
autocomplete: 'url',
autocapitalize: 'none',
size: 'large'
}
},
{
label: 'Password',
prop: 'password',
render: 'input',
renderProps: {
type: 'password',
placeholder: 'Enter password'
},
mobileProps: {
autocomplete: 'new-password',
size: 'large'
}
}
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
2. Mobile Visibility Configuration β
typescript
const adaptiveVisibilityFields = [
{
label: 'Description',
prop: 'description',
render: 'textarea',
renderProps: {
rows: 4,
placeholder: 'Enter detailed description'
},
// Hide this field on mobile
mobileHide: true
},
{
label: 'Remarks',
prop: 'remark',
render: 'input',
renderProps: {
placeholder: 'Optional'
},
// Conditionally hide on mobile
mobileHide: (model) => !model.showAdvanced
},
{
label: 'Advanced Options',
prop: 'advanced',
render: 'switch',
// Show simplified version on mobile
show: (model, item) => {
const isMobile = window.innerWidth < 768
return !isMobile || model.showAdvancedOnMobile
}
}
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Mobile Layout Optimization β
1. Single-Column Layout Configuration β
typescript
const mobileLayoutConfig = {
// Enable single-column layout on mobile
responsiveConfig: {
enabled: true,
mobileSingleColumn: true,
mobileHideLabels: false,
// Mobile-specific breakpoints
breakpoints: {
xs: 480, // Phone portrait
sm: 768, // Phone landscape/tablet portrait
md: 992, // Tablet landscape
lg: 1200, // Small desktop
xl: 1920 // Large desktop
}
}
}
// Manual control of mobile layout
const isMobileLayout = computed(() => {
return formRef.value?.isMobileState() || false
})
// Adjust form configuration based on mobile state
watch(isMobileLayout, (isMobile) => {
if (formRef.value) {
formRef.value.updateOptions(options => ({
...options,
layout: isMobile ? 'grid' : 'flex',
grid: isMobile ? { direction: 'vertical', size: 'large' } : options.grid,
flex: isMobile ? undefined : { gutter: 16 }
}))
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2. Mobile Form Component Sizing β
typescript
const mobileSizedFields = [
{
label: 'Username',
prop: 'username',
render: 'input',
renderProps: {
size: 'default' // Default size on desktop
},
mobileProps: {
size: 'large' // Large size on mobile for better touch
}
},
{
label: 'Selector',
prop: 'selector',
render: 'select',
renderProps: {
size: 'default'
},
mobileProps: {
size: 'large',
teleported: false // Disable teleport on mobile to avoid overlay issues
}
},
{
label: 'Date Selection',
prop: 'date',
render: 'datePicker',
renderProps: {
size: 'default',
type: 'date'
},
mobileProps: {
size: 'large',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
teleported: false
}
}
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Touch Interaction Optimization β
1. Touch-Friendly Component Configuration β
typescript
const touchFriendlyFields = [
{
label: 'Slider Control',
prop: 'slider',
render: 'slider',
renderProps: {
min: 0,
max: 100,
step: 5,
showInput: false // Hide input on mobile
},
mobileProps: {
showInput: false,
height: 6, // Increase slider height for better touch
showTooltip: true,
tooltipClass: 'mobile-slider-tooltip'
}
},
{
label: 'Rating',
prop: 'rating',
render: 'rate',
renderProps: {
max: 5,
allowHalf: false // Disable half stars on mobile
},
mobileProps: {
allowHalf: false,
size: 'large', // Increase star size
textColor: '#ff9900'
}
},
{
label: 'Switch',
prop: 'switch',
render: 'switch',
renderProps: {
size: 'default'
},
mobileProps: {
size: 'large', // Increase switch size for better tapping
width: 60
}
}
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2. Mobile Upload Component β
typescript
const mobileUploadField = {
label: 'File Upload',
prop: 'files',
render: 'upload',
renderProps: {
action: '/api/upload',
listType: 'picture-card',
multiple: true
},
mobileProps: {
listType: 'picture', // Use list style on mobile
drag: false, // Disable drag
showFileList: true,
accept: 'image/*', // Access camera/photo library
capture: 'environment' // Specify camera type
},
renderSlots: {
trigger: () => {
const isMobile = window.innerWidth < 768
return h('el-button', {
type: 'primary',
size: isMobile ? 'large' : 'default',
icon: isMobile ? 'Camera' : 'Upload'
}, isMobile ? 'Take Photo/Select Image' : 'Select File')
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Device Detection and Adaptation β
1. Device Type Detection β
typescript
const deviceDetection = {
// Detect device type
isMobile: () => {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
},
// Detect touch device
isTouchDevice: () => {
return 'ontouchstart' in window || navigator.maxTouchPoints > 0
},
// Detect screen orientation
getOrientation: () => {
return window.innerWidth > window.innerHeight ? 'landscape' : 'portrait'
},
// Get device pixel ratio
getPixelRatio: () => {
return window.devicePixelRatio || 1
}
}
// Adjust form based on device characteristics
const adaptFormToDevice = () => {
const isMobile = deviceDetection.isMobile()
const isTouchDevice = deviceDetection.isTouchDevice()
const orientation = deviceDetection.getOrientation()
const adaptiveOptions = {
mobileBreakpoint: isMobile ? 768 : 576,
responsiveConfig: {
enabled: true,
mobileSingleColumn: isMobile,
mobileHideLabels: isMobile && orientation === 'portrait'
},
layout: isMobile ? 'grid' : 'flex',
grid: isMobile ? {
direction: 'vertical',
size: isTouchDevice ? 'large' : 'medium'
} : undefined
}
formRef.value?.setOptions(adaptiveOptions)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2. Screen Orientation Change Handling β
typescript
// Listen for orientation changes
const handleOrientationChange = () => {
const isPortrait = window.innerHeight > window.innerWidth
// Adjust layout based on orientation
if (formRef.value) {
formRef.value.updateOptions(options => ({
...options,
responsiveConfig: {
...options.responsiveConfig,
mobileHideLabels: isPortrait, // Hide labels in portrait mode
mobileSingleColumn: true
}
}))
// Manually update responsive state
formRef.value.updateResponsiveState()
}
}
// Add event listeners
onMounted(() => {
window.addEventListener('orientationchange', handleOrientationChange)
window.addEventListener('resize', debounce(handleOrientationChange, 300))
})
onUnmounted(() => {
window.removeEventListener('orientationchange', handleOrientationChange)
window.removeEventListener('resize', handleOrientationChange)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
PWA and Mobile Optimization β
1. PWA Form Configuration β
typescript
const pwaFormConfig = {
// Form configuration in PWA environment
responsiveConfig: {
enabled: true,
mobileSingleColumn: true,
breakpoints: {
xs: 320, // Smallest phone screen
sm: 768, // Tablet portrait
md: 1024, // Tablet landscape
lg: 1440, // Desktop
xl: 1920 // Large desktop
}
},
// PWA-specific loading configuration
loadingConfig: {
text: 'Processing...',
background: 'rgba(0, 0, 0, 0.7)',
fullscreen: true, // Use fullscreen loading in PWA
lock: true
}
}
// Offline form handling
const handleOfflineForm = () => {
if (!navigator.onLine) {
// Disable async validation when offline
const items = formRef.value?.getItems() || []
items.forEach(item => {
if (item.asyncValidator) {
formRef.value?.updateItem(item.prop, {
asyncValidator: undefined,
itemProps: {
...item.itemProps,
rules: [
...(item.itemProps?.rules || []),
{ required: true, message: 'This field is required in offline mode', trigger: 'blur' }
]
}
})
}
})
ElMessage.warning('Currently offline, some features are limited')
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2. Mobile Performance Optimization β
typescript
const mobilePerformanceOptimization = {
// Lazy load non-critical fields
lazyLoadFields: (fields: MaFormItem[]) => {
const criticalFields = fields.filter(field => field.priority === 'high')
const nonCriticalFields = fields.filter(field => field.priority !== 'high')
// Load critical fields first
formRef.value?.setItems(criticalFields)
// Lazy load non-critical fields
setTimeout(() => {
nonCriticalFields.forEach(field => {
formRef.value?.appendItem(field)
})
}, 100)
},
// Virtual scrolling for long forms
enableVirtualScrolling: (threshold = 20) => {
const items = formRef.value?.getItems() || []
if (items.length > threshold) {
// Implement virtual scrolling logic
console.log('Enabling virtual scrolling optimization')
}
},
// Image lazy loading
enableImageLazyLoading: () => {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target as HTMLImageElement
img.src = img.dataset.src || ''
observer.unobserve(img)
}
})
})
// Observe images in the form
const images = document.querySelectorAll('img[data-src]')
images.forEach(img => observer.observe(img))
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Testing Mobile Adaptation β
1. Responsive Testing β
typescript
const responsiveTestUtils = {
// Simulate different screen sizes
simulateScreenSize: (width: number, height: number) => {
Object.defineProperty(window, 'innerWidth', {
writable: true,
configurable: true,
value: width
})
Object.defineProperty(window, 'innerHeight', {
writable: true,
configurable: true,
value: height
})
// Trigger resize event
window.dispatchEvent(new Event('resize'))
},
// Test different breakpoints
testBreakpoints: () => {
const breakpoints = [
{ name: 'Mobile Portrait', width: 375, height: 667 },
{ name: 'Mobile Landscape', width: 667, height: 375 },
{ name: 'Tablet Portrait', width: 768, height: 1024 },
{ name: 'Tablet Landscape', width: 1024, height: 768 },
{ name: 'Desktop', width: 1200, height: 800 }
]
breakpoints.forEach(bp => {
responsiveTestUtils.simulateScreenSize(bp.width, bp.height)
console.log(`${bp.name}: isMobile=${formRef.value?.isMobileState()}`)
})
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34