Я пытаюсь достичь динамического поведения в нижнем листе, где пользователи могут плавно прокрутить/расширить стекломорфный контейнер из своего первоначального разрушенного состояния (охватывая около 45% экрана) до полностью расширенного состояния, которое показывает все содержание. Контейнер запускается в нижней половине экрана и расширяется вверх по мере прокрутки пользователя, а высота динамически рассчитывается на основе фактического содержания внутри (включая разделы, такие как заголовки, описания, истории и продукты). Прокрутка должна чувствовать себя естественным и отзывчивым с рациона 1: 1 к прокрутке пользователя. Цель состоит в том, чтобы создать интуитивно понятный, в стиле iOS нижний лист, когда контейнер растет пропорционально его содержанию при сохранении гладких анимаций. на прокрутку, подобно тому, как в обычном прокрутке. Я делаю это неправильно.import AppointmentHistorySheet, { Ref as AppointmentHistoryRef } from '@/components/Appointments/AppointmentHistorySheet';
import { BottomSheetModal } from '@gorhom/bottom-sheet';
import { BlurView } from 'expo-blur';
import { useRouter } from 'expo-router';
import React, { useCallback, useRef } from 'react';
import {
Animated,
Dimensions,
PanResponder,
Text,
TouchableOpacity,
View
} from 'react-native';
import Employees from '../assets/icons/employees.svg';
import History from '../assets/icons/history.svg';
import Map from '../assets/icons/map.svg';
import MapBottomSheet from '../components/Appointments/MapBottomSheet';
import ScheduleAppointmentSheet from '../components/Appointments/ScheduleAppointmentSheet';
import { DescriptionSection, HeadlineSection, StoriesSection } from '../components/Home';
import ProductsSection from '../components/Home/ProductsSection';
import CachedImage from '../components/UI/CachedImage';
import PlaceholderImage from '../components/UI/PlaceholderImage';
import { useAppData } from '../contexts/AppDataContext';
import styles from './index.styles';
const { height: screenHeight, width: screenWidth } = Dimensions.get('window');
// Constants for natural expansion behavior
const INITIAL_HEIGHT = screenHeight * 0.45;
const INITIAL_TOP = screenHeight * 0.55;
const FULL_HEIGHT = screenHeight;
// Calculate content height (estimate based on your sections)
const ESTIMATED_CONTENT_HEIGHT = screenHeight * 1.8; // Increased to accommodate all content including products
const EXPANSION_RANGE = Math.max(
screenHeight * 0.55, // Minimum expansion to reach top
ESTIMATED_CONTENT_HEIGHT - INITIAL_HEIGHT // Additional scroll to see all content
);
const Index = () => {
const scheduleAppointmentRef = useRef(null);
const mapBottomSheetRef = useRef(null);
const appointmentHistoryRef = useRef(null);
const { businessAssets, getMainPictureUrlFromAssets, getDescriptionImageUrlFromAssets } = useAppData();
const router = useRouter();
// Animation values for container transformation
const containerHeight = useRef(new Animated.Value(INITIAL_HEIGHT)).current;
const containerTop = useRef(new Animated.Value(INITIAL_TOP)).current;
// Track cumulative scroll offset
const totalScrollOffset = useRef(0);
// Helper function to update container position
const updateContainerPosition = (offset: number) => {
const clampedOffset = Math.max(0, Math.min(offset, EXPANSION_RANGE));
// Calculate expansion progress for container size
const containerProgress = Math.min(clampedOffset / (screenHeight * 0.55), 1);
// Calculate content scroll progress for internal content positioning
const contentScrollProgress = Math.max(0, (clampedOffset - screenHeight * 0.55) / (EXPANSION_RANGE - screenHeight * 0.55));
// Update container dimensions - allow container to grow beyond screen height to accommodate all content
const maxContainerHeight = Math.max(FULL_HEIGHT, ESTIMATED_CONTENT_HEIGHT);
const newHeight = INITIAL_HEIGHT + (containerProgress * (maxContainerHeight - INITIAL_HEIGHT));
const newTop = INITIAL_TOP - (containerProgress * INITIAL_TOP);
// Apply content scroll offset when container is fully expanded
const contentOffset = contentScrollProgress * (ESTIMATED_CONTENT_HEIGHT - FULL_HEIGHT + 150);
containerHeight.setValue(newHeight);
containerTop.setValue(newTop - contentOffset);
return clampedOffset;
};
// Pan responder for handling gestures (more stable than Gesture API)
const panResponder = PanResponder.create({
onMoveShouldSetPanResponder: (evt, gestureState) => {
return Math.abs(gestureState.dy) > 5;
},
onPanResponderGrant: (evt, gestureState) => {
// Gesture started
},
onPanResponderMove: (evt, gestureState) => {
const newOffset = totalScrollOffset.current - gestureState.dy;
updateContainerPosition(newOffset);
},
onPanResponderRelease: (evt, gestureState) => {
// Update total offset
const newOffset = totalScrollOffset.current - gestureState.dy;
const clampedOffset = updateContainerPosition(newOffset);
totalScrollOffset.current = clampedOffset;
// Simple momentum without complex animations
const velocity = gestureState.vy;
if (Math.abs(velocity) > 0.5) {
const momentumDistance = velocity * -300; // Simple momentum calculation
const targetOffset = Math.max(0, Math.min(clampedOffset + momentumDistance, EXPANSION_RANGE));
if (Math.abs(targetOffset - clampedOffset) > 10) {
Animated.timing(new Animated.Value(clampedOffset), {
toValue: targetOffset,
duration: 300,
useNativeDriver: false,
}).start(({ finished }) => {
if (finished) {
totalScrollOffset.current = targetOffset;
updateContainerPosition(targetOffset);
}
});
}
}
},
});
// Bottom sheet handlers
const handleOpenScheduleSheet = useCallback(() => {
scheduleAppointmentRef.current?.present();
}, []);
const handleOpenMapSheet = useCallback(() => {
mapBottomSheetRef.current?.present();
}, []);
const handleOpenAppointmentHistory = useCallback(() => {
appointmentHistoryRef.current?.present();
}, []);
const handleScheduleSheetDismiss = useCallback(() => {
// Handle cleanup if needed
}, []);
const handleMapSheetDismiss = useCallback(() => {
// Handle cleanup if needed
}, []);
const handleAppointmentHistorySheetDismiss = useCallback(() => {
// Handle cleanup if needed
}, []);
const handleBookingSuccess = useCallback(() => {
appointmentHistoryRef.current?.refresh();
}, []);
const handleStaffPress = useCallback(() => {
router.push('/staffMembers');
}, [router]);
// Image URLs
const mainPictureUrl = businessAssets?.business_number
? getMainPictureUrlFromAssets()
: null;
const descriptionImageUrl = businessAssets?.business_number
? getDescriptionImageUrlFromAssets()
: null;
// Render background content
const renderBackgroundContent = useCallback(() => {
if (mainPictureUrl) {
return (
{renderOverlayContent()}
);
} else {
return (
{renderOverlayContent()}
);
}
}, [mainPictureUrl, businessAssets?.business_number]);
// Render overlay content (icon buttons)
const renderOverlayContent = useCallback(() => (
איך מגיעים
התורים שלך
הצוות שלנו
), [handleOpenMapSheet, handleOpenAppointmentHistory, handleStaffPress]);
return (
{/* Background Image Section */}
{renderBackgroundContent()}
{/* Animated Content Container with Glassmorphism Effect */}
{/* Glassmorphism Blur Effect */}
{/* Visual indicator for expansion */}
{/* Content directly in the animated container */}
{/* Schedule Appointment Bottom Sheet */}
{/* Map Bottom Sheet */}
{/* Appointment History Bottom Sheet */}
);
};
export default Index;
< /code>
И вот файл стиля < /p>
import { Dimensions, StyleSheet } from 'react-native';
import { Colors } from '../constants/colors';
import { GlobalStyles } from '../constants/globalStyles';
const { height: screenHeight, width: screenWidth } = Dimensions.get('window');
const styles = StyleSheet.create({
// Main container with animated positioning and visual styling
scrollContainer: {
position: 'absolute',
left: 0,
right: 0,
backgroundColor: 'transparent', // Changed to transparent for glassmorphism
borderTopLeftRadius: 22,
borderTopRightRadius: 22,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: -2,
},
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 5, // Android shadow
overflow: 'hidden',
zIndex: 10, // Ensure container appears above background
},
// Glassmorphism blur container
glassContainer: {
flex: 1,
backgroundColor: 'rgba(255, 255, 255, 0.90)', // Enhanced semi-transparent background
borderTopLeftRadius: 22,
borderTopRightRadius: 22,
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.3)', // Enhanced border for better glass effect
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 8,
},
shadowOpacity: 0.2,
shadowRadius: 16,
elevation: 12, // Enhanced Android shadow
},
// Content section with proper spacing and alignment
contentSection: {
padding: 20,
marginTop: 15,
alignItems: 'center',
gap: 30,
width: '100%',
minHeight: screenHeight * 0.6, // Increased minimum height to accommodate all content
},
// Overlay button row positioned over background image
overlayButtonRow: {
position: 'absolute',
bottom: 0,
left: 10,
right: 10,
top: screenHeight * 0.3,
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center',
zIndex: 5, // Below scroll container but above background
},
// Icon button column layout
iconButtonColumn: {
flexDirection: 'column',
alignItems: 'center',
gap: 8, // Increased gap for better spacing
},
// Icon button styling
iconButton: {
backgroundColor: Colors.background,
opacity: 0.95, // Slightly more opaque for better visibility
borderRadius: 200,
paddingVertical: 12, // Slightly larger for better touch target
paddingHorizontal: 12,
alignSelf: 'center',
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 3,
},
shadowOpacity: 0.15,
shadowRadius: 6,
elevation: 4, // Android shadow
},
// Icon button label styling
iconButtonLabel: {
...GlobalStyles.caption,
color: Colors.background,
fontSize: 14,
textAlign: 'center',
textShadowColor: 'rgba(0, 0, 0, 0.7)',
textShadowOffset: { width: 1, height: 1 },
textShadowRadius: 3,
fontWeight: '600',
},
// Placeholder background styling
placeholderBackground: {
justifyContent: 'center',
alignItems: 'center',
backgroundColor: Colors.background,
},
// Expansion indicator styling
expansionIndicator: {
position: 'absolute',
top: 12,
left: '50%',
marginLeft: -20,
width: 40,
height: 4,
backgroundColor: Colors.mutedText,
borderRadius: 2,
opacity: 0.4,
},
// Legacy styles that might still be used by other components
scrollHeader: {
alignItems: 'center',
paddingVertical: 15,
backgroundColor: Colors.background,
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
},
sectionTitle: {
...GlobalStyles.heading,
fontSize: 18,
fontWeight: 'bold',
},
sectionText: {
...GlobalStyles.bodyText,
color: Colors.mutedText,
marginTop: 20,
},
contentItem: {
backgroundColor: Colors.background,
borderRadius: 12,
padding: 16,
marginTop: 16,
borderWidth: 1,
borderColor: Colors.border,
},
itemTitle: {
...GlobalStyles.heading,
fontSize: 18,
fontWeight: '600',
marginTop: 8,
},
itemDescription: {
...GlobalStyles.caption,
lineHeight: 20,
},
mainActionButton: {
backgroundColor: Colors.brand,
borderRadius: 200,
paddingVertical: 10,
paddingHorizontal: 24,
alignItems: 'center',
width: '50%',
},
mainActionButtonText: {
...GlobalStyles.buttonText,
fontSize: 16,
},
});
export default styles;
Подробнее здесь: https://stackoverflow.com/questions/797 ... act-native
Расширение взгляда при прокрутке реагирует натив ⇐ CSS
Разбираемся в CSS
-
Anonymous
1753777032
Anonymous
Я пытаюсь достичь динамического поведения в нижнем листе, где пользователи могут плавно прокрутить/расширить стекломорфный контейнер из своего первоначального разрушенного состояния (охватывая около 45% экрана) до полностью расширенного состояния, которое показывает все содержание. Контейнер запускается в нижней половине экрана и расширяется вверх по мере прокрутки пользователя, а высота динамически рассчитывается на основе фактического содержания внутри (включая разделы, такие как заголовки, описания, истории и продукты). Прокрутка должна чувствовать себя естественным и отзывчивым с рациона 1: 1 к прокрутке пользователя. Цель состоит в том, чтобы создать интуитивно понятный, в стиле iOS нижний лист, когда контейнер растет пропорционально его содержанию при сохранении гладких анимаций. на прокрутку, подобно тому, как в обычном прокрутке. Я делаю это неправильно.import AppointmentHistorySheet, { Ref as AppointmentHistoryRef } from '@/components/Appointments/AppointmentHistorySheet';
import { BottomSheetModal } from '@gorhom/bottom-sheet';
import { BlurView } from 'expo-blur';
import { useRouter } from 'expo-router';
import React, { useCallback, useRef } from 'react';
import {
Animated,
Dimensions,
PanResponder,
Text,
TouchableOpacity,
View
} from 'react-native';
import Employees from '../assets/icons/employees.svg';
import History from '../assets/icons/history.svg';
import Map from '../assets/icons/map.svg';
import MapBottomSheet from '../components/Appointments/MapBottomSheet';
import ScheduleAppointmentSheet from '../components/Appointments/ScheduleAppointmentSheet';
import { DescriptionSection, HeadlineSection, StoriesSection } from '../components/Home';
import ProductsSection from '../components/Home/ProductsSection';
import CachedImage from '../components/UI/CachedImage';
import PlaceholderImage from '../components/UI/PlaceholderImage';
import { useAppData } from '../contexts/AppDataContext';
import styles from './index.styles';
const { height: screenHeight, width: screenWidth } = Dimensions.get('window');
// Constants for natural expansion behavior
const INITIAL_HEIGHT = screenHeight * 0.45;
const INITIAL_TOP = screenHeight * 0.55;
const FULL_HEIGHT = screenHeight;
// Calculate content height (estimate based on your sections)
const ESTIMATED_CONTENT_HEIGHT = screenHeight * 1.8; // Increased to accommodate all content including products
const EXPANSION_RANGE = Math.max(
screenHeight * 0.55, // Minimum expansion to reach top
ESTIMATED_CONTENT_HEIGHT - INITIAL_HEIGHT // Additional scroll to see all content
);
const Index = () => {
const scheduleAppointmentRef = useRef(null);
const mapBottomSheetRef = useRef(null);
const appointmentHistoryRef = useRef(null);
const { businessAssets, getMainPictureUrlFromAssets, getDescriptionImageUrlFromAssets } = useAppData();
const router = useRouter();
// Animation values for container transformation
const containerHeight = useRef(new Animated.Value(INITIAL_HEIGHT)).current;
const containerTop = useRef(new Animated.Value(INITIAL_TOP)).current;
// Track cumulative scroll offset
const totalScrollOffset = useRef(0);
// Helper function to update container position
const updateContainerPosition = (offset: number) => {
const clampedOffset = Math.max(0, Math.min(offset, EXPANSION_RANGE));
// Calculate expansion progress for container size
const containerProgress = Math.min(clampedOffset / (screenHeight * 0.55), 1);
// Calculate content scroll progress for internal content positioning
const contentScrollProgress = Math.max(0, (clampedOffset - screenHeight * 0.55) / (EXPANSION_RANGE - screenHeight * 0.55));
// Update container dimensions - allow container to grow beyond screen height to accommodate all content
const maxContainerHeight = Math.max(FULL_HEIGHT, ESTIMATED_CONTENT_HEIGHT);
const newHeight = INITIAL_HEIGHT + (containerProgress * (maxContainerHeight - INITIAL_HEIGHT));
const newTop = INITIAL_TOP - (containerProgress * INITIAL_TOP);
// Apply content scroll offset when container is fully expanded
const contentOffset = contentScrollProgress * (ESTIMATED_CONTENT_HEIGHT - FULL_HEIGHT + 150);
containerHeight.setValue(newHeight);
containerTop.setValue(newTop - contentOffset);
return clampedOffset;
};
// Pan responder for handling gestures (more stable than Gesture API)
const panResponder = PanResponder.create({
onMoveShouldSetPanResponder: (evt, gestureState) => {
return Math.abs(gestureState.dy) > 5;
},
onPanResponderGrant: (evt, gestureState) => {
// Gesture started
},
onPanResponderMove: (evt, gestureState) => {
const newOffset = totalScrollOffset.current - gestureState.dy;
updateContainerPosition(newOffset);
},
onPanResponderRelease: (evt, gestureState) => {
// Update total offset
const newOffset = totalScrollOffset.current - gestureState.dy;
const clampedOffset = updateContainerPosition(newOffset);
totalScrollOffset.current = clampedOffset;
// Simple momentum without complex animations
const velocity = gestureState.vy;
if (Math.abs(velocity) > 0.5) {
const momentumDistance = velocity * -300; // Simple momentum calculation
const targetOffset = Math.max(0, Math.min(clampedOffset + momentumDistance, EXPANSION_RANGE));
if (Math.abs(targetOffset - clampedOffset) > 10) {
Animated.timing(new Animated.Value(clampedOffset), {
toValue: targetOffset,
duration: 300,
useNativeDriver: false,
}).start(({ finished }) => {
if (finished) {
totalScrollOffset.current = targetOffset;
updateContainerPosition(targetOffset);
}
});
}
}
},
});
// Bottom sheet handlers
const handleOpenScheduleSheet = useCallback(() => {
scheduleAppointmentRef.current?.present();
}, []);
const handleOpenMapSheet = useCallback(() => {
mapBottomSheetRef.current?.present();
}, []);
const handleOpenAppointmentHistory = useCallback(() => {
appointmentHistoryRef.current?.present();
}, []);
const handleScheduleSheetDismiss = useCallback(() => {
// Handle cleanup if needed
}, []);
const handleMapSheetDismiss = useCallback(() => {
// Handle cleanup if needed
}, []);
const handleAppointmentHistorySheetDismiss = useCallback(() => {
// Handle cleanup if needed
}, []);
const handleBookingSuccess = useCallback(() => {
appointmentHistoryRef.current?.refresh();
}, []);
const handleStaffPress = useCallback(() => {
router.push('/staffMembers');
}, [router]);
// Image URLs
const mainPictureUrl = businessAssets?.business_number
? getMainPictureUrlFromAssets()
: null;
const descriptionImageUrl = businessAssets?.business_number
? getDescriptionImageUrlFromAssets()
: null;
// Render background content
const renderBackgroundContent = useCallback(() => {
if (mainPictureUrl) {
return (
{renderOverlayContent()}
);
} else {
return (
{renderOverlayContent()}
);
}
}, [mainPictureUrl, businessAssets?.business_number]);
// Render overlay content (icon buttons)
const renderOverlayContent = useCallback(() => (
איך מגיעים
התורים שלך
הצוות שלנו
), [handleOpenMapSheet, handleOpenAppointmentHistory, handleStaffPress]);
return (
{/* Background Image Section */}
{renderBackgroundContent()}
{/* Animated Content Container with Glassmorphism Effect */}
{/* Glassmorphism Blur Effect */}
{/* Visual indicator for expansion */}
{/* Content directly in the animated container */}
{/* Schedule Appointment Bottom Sheet */}
{/* Map Bottom Sheet */}
{/* Appointment History Bottom Sheet */}
);
};
export default Index;
< /code>
И вот файл стиля < /p>
import { Dimensions, StyleSheet } from 'react-native';
import { Colors } from '../constants/colors';
import { GlobalStyles } from '../constants/globalStyles';
const { height: screenHeight, width: screenWidth } = Dimensions.get('window');
const styles = StyleSheet.create({
// Main container with animated positioning and visual styling
scrollContainer: {
position: 'absolute',
left: 0,
right: 0,
backgroundColor: 'transparent', // Changed to transparent for glassmorphism
borderTopLeftRadius: 22,
borderTopRightRadius: 22,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: -2,
},
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 5, // Android shadow
overflow: 'hidden',
zIndex: 10, // Ensure container appears above background
},
// Glassmorphism blur container
glassContainer: {
flex: 1,
backgroundColor: 'rgba(255, 255, 255, 0.90)', // Enhanced semi-transparent background
borderTopLeftRadius: 22,
borderTopRightRadius: 22,
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.3)', // Enhanced border for better glass effect
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 8,
},
shadowOpacity: 0.2,
shadowRadius: 16,
elevation: 12, // Enhanced Android shadow
},
// Content section with proper spacing and alignment
contentSection: {
padding: 20,
marginTop: 15,
alignItems: 'center',
gap: 30,
width: '100%',
minHeight: screenHeight * 0.6, // Increased minimum height to accommodate all content
},
// Overlay button row positioned over background image
overlayButtonRow: {
position: 'absolute',
bottom: 0,
left: 10,
right: 10,
top: screenHeight * 0.3,
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center',
zIndex: 5, // Below scroll container but above background
},
// Icon button column layout
iconButtonColumn: {
flexDirection: 'column',
alignItems: 'center',
gap: 8, // Increased gap for better spacing
},
// Icon button styling
iconButton: {
backgroundColor: Colors.background,
opacity: 0.95, // Slightly more opaque for better visibility
borderRadius: 200,
paddingVertical: 12, // Slightly larger for better touch target
paddingHorizontal: 12,
alignSelf: 'center',
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 3,
},
shadowOpacity: 0.15,
shadowRadius: 6,
elevation: 4, // Android shadow
},
// Icon button label styling
iconButtonLabel: {
...GlobalStyles.caption,
color: Colors.background,
fontSize: 14,
textAlign: 'center',
textShadowColor: 'rgba(0, 0, 0, 0.7)',
textShadowOffset: { width: 1, height: 1 },
textShadowRadius: 3,
fontWeight: '600',
},
// Placeholder background styling
placeholderBackground: {
justifyContent: 'center',
alignItems: 'center',
backgroundColor: Colors.background,
},
// Expansion indicator styling
expansionIndicator: {
position: 'absolute',
top: 12,
left: '50%',
marginLeft: -20,
width: 40,
height: 4,
backgroundColor: Colors.mutedText,
borderRadius: 2,
opacity: 0.4,
},
// Legacy styles that might still be used by other components
scrollHeader: {
alignItems: 'center',
paddingVertical: 15,
backgroundColor: Colors.background,
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
},
sectionTitle: {
...GlobalStyles.heading,
fontSize: 18,
fontWeight: 'bold',
},
sectionText: {
...GlobalStyles.bodyText,
color: Colors.mutedText,
marginTop: 20,
},
contentItem: {
backgroundColor: Colors.background,
borderRadius: 12,
padding: 16,
marginTop: 16,
borderWidth: 1,
borderColor: Colors.border,
},
itemTitle: {
...GlobalStyles.heading,
fontSize: 18,
fontWeight: '600',
marginTop: 8,
},
itemDescription: {
...GlobalStyles.caption,
lineHeight: 20,
},
mainActionButton: {
backgroundColor: Colors.brand,
borderRadius: 200,
paddingVertical: 10,
paddingHorizontal: 24,
alignItems: 'center',
width: '50%',
},
mainActionButtonText: {
...GlobalStyles.buttonText,
fontSize: 16,
},
});
export default styles;
Подробнее здесь: [url]https://stackoverflow.com/questions/79718299/expanding-view-when-scrolling-react-native[/url]
Ответить
1 сообщение
• Страница 1 из 1
Перейти
- Кемерово-IT
- ↳ Javascript
- ↳ C#
- ↳ JAVA
- ↳ Elasticsearch aggregation
- ↳ Python
- ↳ Php
- ↳ Android
- ↳ Html
- ↳ Jquery
- ↳ C++
- ↳ IOS
- ↳ CSS
- ↳ Excel
- ↳ Linux
- ↳ Apache
- ↳ MySql
- Детский мир
- Для души
- ↳ Музыкальные инструменты даром
- ↳ Печатная продукция даром
- Внешняя красота и здоровье
- ↳ Одежда и обувь для взрослых даром
- ↳ Товары для здоровья
- ↳ Физкультура и спорт
- Техника - даром!
- ↳ Автомобилистам
- ↳ Компьютерная техника
- ↳ Плиты: газовые и электрические
- ↳ Холодильники
- ↳ Стиральные машины
- ↳ Телевизоры
- ↳ Телефоны, смартфоны, плашеты
- ↳ Швейные машинки
- ↳ Прочая электроника и техника
- ↳ Фототехника
- Ремонт и интерьер
- ↳ Стройматериалы, инструмент
- ↳ Мебель и предметы интерьера даром
- ↳ Cантехника
- Другие темы
- ↳ Разное даром
- ↳ Давай меняться!
- ↳ Отдам\возьму за копеечку
- ↳ Работа и подработка в Кемерове
- ↳ Давай с тобой поговорим...
Мобильная версия