Anonymous
Двойная подсветка во Flutter Web
Сообщение
Anonymous » 23 ноя 2025, 16:02
При выделении текста в текстовом поле => Отображается два выделения, а при прокрутке остальные выделения остаются в том же положении.
При выделении текста:
При прокрутке:
Это происходит только в сети Flutter, поэтому я предполагаю, что причина в разном поведении между Flutter Canvas и веб-DOM.
Среда
Flutter 3.38.2 • стабильный канал • https://github.com/flutter/flutter.git
Framework • версия f5a8537f90 (5 дней назад) • 2025-11-18 09:27:21 -0500
Двигатель • хэш 78c3c9557e50ee7c676fa37562558c59efd8406a (версия b5990e5ccc) (10 дней назад) •
12.11.2025 21:08:24.000Z
Инструменты • Dart 3.10.0 • DevTools 2.51.1
Safari на симуляторе iOS ( iPhone 13 Pro, iOS 18.6 )
Chrome на iPhone 13 Pro Max, iOS 18.7.2
[*]Связанные коды
Код: Выделить всё
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart' show kIsWeb, kReleaseMode;
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'firebase_options.dart' as prod;
import 'firebase_options_stg.dart' as stg;
import 'firebase_emulator.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'l10n/app_localizations.dart';
import 'client/screens/start_page.dart';
import 'client/screens/login_page.dart';
import 'client/screens/signup_page.dart';
import 'client/screens/questions_page.dart';
import 'client/screens/home_page.dart';
import 'auth_required.dart';
import 'client/screens/plan_proposal_page.dart';
import 'client/screens/settings_page.dart';
import 'client/screens/weight_history_screen.dart';
import 'coach/screens/coach_dashboard_page.dart';
import 'coach/screens/coach_login_page.dart';
import 'utils/auth_redirect_wrapper.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Select Firebase options by build-time environment.
// - Debug/Profile: default to "stg" unless FIREBASE_ENV is explicitly provided
// - Release: default to "prod" unless FIREBASE_ENV is explicitly provided
const String envDefine = String.fromEnvironment(
'FIREBASE_ENV',
defaultValue: '',
);
final String env = envDefine.isEmpty
? (kReleaseMode ? 'prod' : 'stg')
: envDefine;
final FirebaseOptions firebaseOptions = env == 'stg'
? stg.DefaultFirebaseOptions.currentPlatform
: prod.DefaultFirebaseOptions.currentPlatform;
await Firebase.initializeApp(options: firebaseOptions);
await connectFirebaseToEmulatorIfNeeded();
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
builder: (context, child) {
const bool useEmulator = bool.fromEnvironment(
'USE_FIREBASE_EMULATOR',
// Default to emulator in non-release builds for safety
defaultValue: !kReleaseMode,
);
final double bottom = (kIsWeb && useEmulator) ? 40.0 : 0.0;
if (bottom == 0.0) return child!;
return Padding(
padding: EdgeInsets.only(bottom: bottom),
child: child,
);
},
onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle,
theme: () {
const double webLineHeight = 1.3;
final baseTheme = ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
fontFamily: 'NotoSansJP',
);
final TextTheme baseTextTheme = baseTheme.textTheme;
final TextTheme adjustedTextTheme = kIsWeb
? baseTextTheme.copyWith(
// Fix line height for all text styles on web to align browser and Flutter selection highlights
displayLarge: baseTextTheme.displayLarge?.copyWith(
height: webLineHeight,
),
displayMedium: baseTextTheme.displayMedium?.copyWith(
height: webLineHeight,
),
displaySmall: baseTextTheme.displaySmall?.copyWith(
height: webLineHeight,
),
headlineLarge: baseTextTheme.headlineLarge?.copyWith(
height: webLineHeight,
),
headlineMedium: baseTextTheme.headlineMedium?.copyWith(
height: webLineHeight,
),
headlineSmall: baseTextTheme.headlineSmall?.copyWith(
height: webLineHeight,
),
titleLarge: baseTextTheme.titleLarge?.copyWith(
height: webLineHeight,
),
titleMedium: baseTextTheme.titleMedium?.copyWith(
height: webLineHeight,
),
titleSmall: baseTextTheme.titleSmall?.copyWith(
height: webLineHeight,
),
bodyLarge: baseTextTheme.bodyLarge?.copyWith(
height: webLineHeight,
),
bodyMedium: baseTextTheme.bodyMedium?.copyWith(
height: webLineHeight,
),
bodySmall: baseTextTheme.bodySmall?.copyWith(
height: webLineHeight,
),
labelLarge: baseTextTheme.labelLarge?.copyWith(
height: webLineHeight,
),
labelMedium: baseTextTheme.labelMedium?.copyWith(
height: webLineHeight,
),
labelSmall: baseTextTheme.labelSmall?.copyWith(
height: webLineHeight,
),
)
: baseTextTheme;
return baseTheme.copyWith(
textTheme: adjustedTextTheme,
primaryTextTheme: adjustedTextTheme,
);
}(),
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
AppLocalizations.delegate,
],
supportedLocales: AppLocalizations.supportedLocales,
initialRoute: '/start',
routes: {
'/start': (_) => const AuthRedirectWrapper(child: StartPage()),
'/login': (_) => const AuthRedirectWrapper(child: LoginPage()),
'/signup': (_) => const SignUpPage(),
'/questions': (_) => const QuestionsPage(),
'/home': (_) =>
const AuthRequired(requiredRole: 'client', child: HomePage()),
'/planProposal': (_) => const PlanProposalPage(),
'/settings': (_) =>
const AuthRequired(requiredRole: 'client', child: SettingsPage()),
'/weightHistory': (_) => const AuthRequired(
requiredRole: 'client',
child: WeightHistoryScreen(),
),
'/coach/login': (_) =>
const AuthRedirectWrapper(child: CoachLoginPage()),
'/coach/dashboard': (_) => const AuthRequired(
requiredRole: 'coach',
child: CoachDashboardPage(),
),
},
);
}
}
Раздел текстового поля, в котором возникает проблема
Код: Выделить всё
TextField(
controller: _controller,
maxLines: null,
decoration: const InputDecoration(border: OutlineInputBorder()),
style: Theme.of(context).textTheme.bodyMedium,
),
Примечание. Я попробовал следующие два способа добавления в раздел в index.html, но ни один из них не сработал.
/* Сохраняйте выделение Flutter; скрыть выбор браузера для скрытого элемента редактирования */
.flt-text-editing::selection {
background-color: Transparent !important;
color: Transparent !important; /* Скрыть текст, сохранить работу логики курсора */
}
.flt-text-editing::-moz-selection {
background-color: Transparent !important;
color: Transparent !important;
}
Код: Выделить всё
\
/\* Align hidden editing element font metrics with Flutter TextField \*/
.flt-text-editing {
font-family: "NotoSansJP", system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", sans-serif !important;
font-size: 14px !important; /\* Match bodyMedium default fontSize \*/
line-height: 1.3 !important; /\* Match TextStyle.height: 1.3 \*/
letter-spacing: 0 !important; /\* Default letter spacing \*/
}
\
Подробнее здесь:
https://stackoverflow.com/questions/798 ... lutter-web
1763902934
Anonymous
При выделении текста в текстовом поле => Отображается два выделения, а при прокрутке остальные выделения остаются в том же положении. При выделении текста: [img]https://i.sstatic.net/MBnt3VQp.png[/img] При прокрутке: [img]https://i.sstatic.net/bmHHLS2U.png[/img] Это происходит только в сети Flutter, поэтому я предполагаю, что причина в разном поведении между Flutter Canvas и веб-DOM. [list] [*]Среда Flutter 3.38.2 • стабильный канал • https://github.com/flutter/flutter.git Framework • версия f5a8537f90 (5 дней назад) • 2025-11-18 09:27:21 -0500 Двигатель • хэш 78c3c9557e50ee7c676fa37562558c59efd8406a (версия b5990e5ccc) (10 дней назад) • 12.11.2025 21:08:24.000Z Инструменты • Dart 3.10.0 • DevTools 2.51.1 [*]Safari на симуляторе iOS ( iPhone 13 Pro, iOS 18.6 ) [*]Chrome на iPhone 13 Pro Max, iOS 18.7.2 [/list] [*]Связанные коды [list] main.dart [/list] [code]import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart' show kIsWeb, kReleaseMode; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'firebase_options.dart' as prod; import 'firebase_options_stg.dart' as stg; import 'firebase_emulator.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'l10n/app_localizations.dart'; import 'client/screens/start_page.dart'; import 'client/screens/login_page.dart'; import 'client/screens/signup_page.dart'; import 'client/screens/questions_page.dart'; import 'client/screens/home_page.dart'; import 'auth_required.dart'; import 'client/screens/plan_proposal_page.dart'; import 'client/screens/settings_page.dart'; import 'client/screens/weight_history_screen.dart'; import 'coach/screens/coach_dashboard_page.dart'; import 'coach/screens/coach_login_page.dart'; import 'utils/auth_redirect_wrapper.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); // Select Firebase options by build-time environment. // - Debug/Profile: default to "stg" unless FIREBASE_ENV is explicitly provided // - Release: default to "prod" unless FIREBASE_ENV is explicitly provided const String envDefine = String.fromEnvironment( 'FIREBASE_ENV', defaultValue: '', ); final String env = envDefine.isEmpty ? (kReleaseMode ? 'prod' : 'stg') : envDefine; final FirebaseOptions firebaseOptions = env == 'stg' ? stg.DefaultFirebaseOptions.currentPlatform : prod.DefaultFirebaseOptions.currentPlatform; await Firebase.initializeApp(options: firebaseOptions); await connectFirebaseToEmulatorIfNeeded(); runApp(const ProviderScope(child: MyApp())); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( builder: (context, child) { const bool useEmulator = bool.fromEnvironment( 'USE_FIREBASE_EMULATOR', // Default to emulator in non-release builds for safety defaultValue: !kReleaseMode, ); final double bottom = (kIsWeb && useEmulator) ? 40.0 : 0.0; if (bottom == 0.0) return child!; return Padding( padding: EdgeInsets.only(bottom: bottom), child: child, ); }, onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle, theme: () { const double webLineHeight = 1.3; final baseTheme = ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), fontFamily: 'NotoSansJP', ); final TextTheme baseTextTheme = baseTheme.textTheme; final TextTheme adjustedTextTheme = kIsWeb ? baseTextTheme.copyWith( // Fix line height for all text styles on web to align browser and Flutter selection highlights displayLarge: baseTextTheme.displayLarge?.copyWith( height: webLineHeight, ), displayMedium: baseTextTheme.displayMedium?.copyWith( height: webLineHeight, ), displaySmall: baseTextTheme.displaySmall?.copyWith( height: webLineHeight, ), headlineLarge: baseTextTheme.headlineLarge?.copyWith( height: webLineHeight, ), headlineMedium: baseTextTheme.headlineMedium?.copyWith( height: webLineHeight, ), headlineSmall: baseTextTheme.headlineSmall?.copyWith( height: webLineHeight, ), titleLarge: baseTextTheme.titleLarge?.copyWith( height: webLineHeight, ), titleMedium: baseTextTheme.titleMedium?.copyWith( height: webLineHeight, ), titleSmall: baseTextTheme.titleSmall?.copyWith( height: webLineHeight, ), bodyLarge: baseTextTheme.bodyLarge?.copyWith( height: webLineHeight, ), bodyMedium: baseTextTheme.bodyMedium?.copyWith( height: webLineHeight, ), bodySmall: baseTextTheme.bodySmall?.copyWith( height: webLineHeight, ), labelLarge: baseTextTheme.labelLarge?.copyWith( height: webLineHeight, ), labelMedium: baseTextTheme.labelMedium?.copyWith( height: webLineHeight, ), labelSmall: baseTextTheme.labelSmall?.copyWith( height: webLineHeight, ), ) : baseTextTheme; return baseTheme.copyWith( textTheme: adjustedTextTheme, primaryTextTheme: adjustedTextTheme, ); }(), localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, AppLocalizations.delegate, ], supportedLocales: AppLocalizations.supportedLocales, initialRoute: '/start', routes: { '/start': (_) => const AuthRedirectWrapper(child: StartPage()), '/login': (_) => const AuthRedirectWrapper(child: LoginPage()), '/signup': (_) => const SignUpPage(), '/questions': (_) => const QuestionsPage(), '/home': (_) => const AuthRequired(requiredRole: 'client', child: HomePage()), '/planProposal': (_) => const PlanProposalPage(), '/settings': (_) => const AuthRequired(requiredRole: 'client', child: SettingsPage()), '/weightHistory': (_) => const AuthRequired( requiredRole: 'client', child: WeightHistoryScreen(), ), '/coach/login': (_) => const AuthRedirectWrapper(child: CoachLoginPage()), '/coach/dashboard': (_) => const AuthRequired( requiredRole: 'coach', child: CoachDashboardPage(), ), }, ); } } [/code] [list] [*]web/index.html [/list] [code] [*] nutrition_coaching_app [/code] [list] Раздел текстового поля, в котором возникает проблема [/list] [code]TextField( controller: _controller, maxLines: null, decoration: const InputDecoration(border: OutlineInputBorder()), style: Theme.of(context).textTheme.bodyMedium, ), [/code] Примечание. Я попробовал следующие два способа добавления в раздел в index.html, но ни один из них не сработал. [list] [*] /* Сохраняйте выделение Flutter; скрыть выбор браузера для скрытого элемента редактирования */ .flt-text-editing::selection { background-color: Transparent !important; color: Transparent !important; /* Скрыть текст, сохранить работу логики курсора */ } .flt-text-editing::-moz-selection { background-color: Transparent !important; color: Transparent !important; } [*] [code]\ /\* Align hidden editing element font metrics with Flutter TextField \*/ .flt-text-editing { font-family: "NotoSansJP", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important; font-size: 14px !important; /\* Match bodyMedium default fontSize \*/ line-height: 1.3 !important; /\* Match TextStyle.height: 1.3 \*/ letter-spacing: 0 !important; /\* Default letter spacing \*/ } \ [/code] [/list] Подробнее здесь: [url]https://stackoverflow.com/questions/79827717/double-highlight-in-flutter-web[/url]