├── .prettierignore
├── README.md
├── public
└── img
│ ├── add-dpr.png
│ ├── add-dpr_.png
│ ├── check-img.png
│ ├── add-device.png
│ ├── check-fonts.png
│ ├── colored-text.png
│ ├── devices-list.png
│ ├── pixel-glass.png
│ ├── switch-dpr.png
│ └── devices-button.png
├── .gitignore
├── src
├── components
│ ├── siteFooter
│ │ ├── siteFooter.module.scss
│ │ └── index.tsx
│ ├── asideNav
│ │ ├── asideNav.module.scss
│ │ └── index.tsx
│ ├── aside
│ │ ├── aside.module.scss
│ │ └── index.tsx
│ ├── customHead
│ │ └── index.tsx
│ ├── siteNav
│ │ ├── siteNav.module.scss
│ │ └── index.tsx
│ ├── socials
│ │ ├── socials.module.scss
│ │ └── index.tsx
│ ├── siteHeader
│ │ ├── siteHeader.module.scss
│ │ └── index.tsx
│ ├── article
│ │ ├── article.module.scss
│ │ └── index.tsx
│ ├── layout
│ │ ├── layout.module.scss
│ │ └── index.tsx
│ └── icons
│ │ └── index.tsx
├── config.json
├── postsList.ts
├── pages
│ ├── _document.tsx
│ ├── _app.tsx
│ ├── _error.tsx
│ ├── 404.tsx
│ ├── index.tsx
│ └── [slug].tsx
├── types.ts
├── utils
│ ├── collectPostsUrls.ts
│ ├── markdownToHtml.ts
│ └── api.ts
└── styles
│ ├── themes.scss
│ ├── prism.scss
│ └── global.scss
├── .editorconfig
├── next-env.d.ts
├── next.config.js
├── .prettierrc.json
├── how-to.md
├── package.json
├── tsconfig.json
├── _posts
├── catching-bug.md
├── index.md
├── drop-of-magic.md
├── first-steps.md
├── check-code.md
├── bem-rules.md
├── accessibility.md
└── examples.md
├── .github
└── workflows
│ └── nextjs.yml
└── .stylelintrc
/.prettierignore:
--------------------------------------------------------------------------------
1 | *.md
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Подсказки по разметке HTML-документа
2 |
3 | http://yoksel.github.io/easy-markup
4 |
--------------------------------------------------------------------------------
/public/img/add-dpr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoksel/easy-markup/HEAD/public/img/add-dpr.png
--------------------------------------------------------------------------------
/public/img/add-dpr_.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoksel/easy-markup/HEAD/public/img/add-dpr_.png
--------------------------------------------------------------------------------
/public/img/check-img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoksel/easy-markup/HEAD/public/img/check-img.png
--------------------------------------------------------------------------------
/public/img/add-device.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoksel/easy-markup/HEAD/public/img/add-device.png
--------------------------------------------------------------------------------
/public/img/check-fonts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoksel/easy-markup/HEAD/public/img/check-fonts.png
--------------------------------------------------------------------------------
/public/img/colored-text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoksel/easy-markup/HEAD/public/img/colored-text.png
--------------------------------------------------------------------------------
/public/img/devices-list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoksel/easy-markup/HEAD/public/img/devices-list.png
--------------------------------------------------------------------------------
/public/img/pixel-glass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoksel/easy-markup/HEAD/public/img/pixel-glass.png
--------------------------------------------------------------------------------
/public/img/switch-dpr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoksel/easy-markup/HEAD/public/img/switch-dpr.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | _site
2 | npm-debug.log
3 | .DS_Store
4 | .next
5 | node_modules
6 | tsconfig.tsbuildinfo
7 | out
8 |
--------------------------------------------------------------------------------
/public/img/devices-button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoksel/easy-markup/HEAD/public/img/devices-button.png
--------------------------------------------------------------------------------
/src/components/siteFooter/siteFooter.module.scss:
--------------------------------------------------------------------------------
1 | .siteFooter {
2 | margin-top: 3rem;
3 | padding-top: 1.5rem;
4 | border-top: 3px solid var(--color-border);
5 | }
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/src/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Простые правила разметки",
3 | "description": "Простые правила разметки HTML-документов, рекомендации по выбору HTML-элементов, примеры кода",
4 | "baseurl": "/easy-markup"
5 | }
6 |
--------------------------------------------------------------------------------
/src/postsList.ts:
--------------------------------------------------------------------------------
1 | /* npm run getPosts */
2 |
3 | const postList = ['/accessibility', '/bem-rules', '/catching-bug', '/check-code', '/drop-of-magic', '/examples', '/first-steps', '/index'];
4 |
5 | export default postList;
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
13 | Вернуться на главную 14 |
15 |Страница не найдена
21 |22 | Вернуться на главную 23 |
24 |${highlightedCode}[^`]+)```/gm,
24 | addHighlighting,
25 | );
26 |
27 | // Wrap text in titles with link to enable anchors
28 | const withAnchors = withCodeBlocks.replace(
29 | /[2-5]) id="(?[^"]+)[^>]+>(?[^\<]+)(.*)<\/h[2-5]>/gm,
30 | addAnchors,
31 | );
32 | return withAnchors;
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/aside/index.tsx:
--------------------------------------------------------------------------------
1 | import classnames from 'classnames';
2 | import styles from './aside.module.scss';
3 | import AsideNav from '../asideNav';
4 | import { Post } from '../../types';
5 |
6 | const projects = [
7 | {
8 | text: 'Простые правила разметки',
9 | url: '/',
10 | },
11 | {
12 | text: 'Простой CSS',
13 | url: 'https://yoksel.github.io/easy-css/',
14 | },
15 | {
16 | text: 'HTML & CSS: как не надо',
17 | url: 'https://yoksel.github.io/bad-practices/',
18 | },
19 | ];
20 |
21 | const Aside = ({
22 | className,
23 | slug,
24 | post,
25 | }: {
26 | className: string;
27 | slug: string;
28 | post: Post;
29 | }) => {
30 | const { links, additional_links } = post;
31 |
32 | return (
33 |
56 | );
57 | };
58 |
59 | export default Aside;
60 |
--------------------------------------------------------------------------------
/src/components/socials/index.tsx:
--------------------------------------------------------------------------------
1 | import { PageUrl } from '../../types';
2 | import { GithubIcon, TwitterIcon } from '../icons';
3 | import styles from './socials.module.scss';
4 | import classnames from 'classnames';
5 |
6 | interface SocialUrl extends PageUrl {
7 | type: 'github' | 'twitter';
8 | icon: React.ReactNode;
9 | }
10 |
11 | const items: SocialUrl[] = [
12 | {
13 | url: 'https://github.com/yoksel/easy-markup/',
14 | text: 'Github',
15 | type: 'github',
16 | icon: ,
17 | },
18 | {
19 | url: 'https://twitter.com/yoksel',
20 | text: 'Twitter',
21 | type: 'twitter',
22 | icon: ,
23 | },
24 | ];
25 |
26 | const Socials = () => (
27 |
28 | {items.map((item, index) => (
29 | -
33 |
38 |
44 | {item.icon}
45 |
46 |
47 |
48 | ))}
49 |
50 | );
51 |
52 | export default Socials;
53 |
--------------------------------------------------------------------------------
/src/pages/[slug].tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import type {
3 | InferGetStaticPropsType,
4 | GetStaticProps,
5 | GetStaticPaths,
6 | } from 'next';
7 | import { getAllPosts, getPostBySlug } from '../utils/api';
8 | import markdownToHtml from '../utils/markdownToHtml';
9 | import Article from '../components/article';
10 | import Layout from '../components/layout';
11 | import postsList from '../postsList';
12 | import { Post } from '../types';
13 |
14 | export const getStaticPaths = (async () => {
15 | return {
16 | // List of all paths which should be created during the build
17 | paths: postsList,
18 | fallback: false, // false or "blocking"
19 | };
20 | }) satisfies GetStaticPaths;
21 |
22 | export const getStaticProps = (async (context) => {
23 | const { slug } = context?.params || {};
24 |
25 | if (!slug || Array.isArray(slug)) {
26 | throw new Error('No page path');
27 | }
28 |
29 | const post = getPostBySlug({
30 | slug,
31 | fields: ['title', 'slug', 'content', 'links', 'additional_links'],
32 | });
33 |
34 | if (!post) {
35 | throw new Error('Post not found');
36 | }
37 |
38 | const allPosts = getAllPosts(['title', 'slug', 'order']);
39 | return { props: { post, allPosts } };
40 | }) satisfies GetStaticProps<{
41 | post: Post;
42 | allPosts: Post[];
43 | }>;
44 |
45 | export default function Page({
46 | post,
47 | allPosts,
48 | }: InferGetStaticPropsType) {
49 | const content = markdownToHtml(post?.content || '');
50 |
51 | return (
52 |
57 |
61 |
62 | );
63 | }
64 |
--------------------------------------------------------------------------------
/src/utils/api.ts:
--------------------------------------------------------------------------------
1 | // https://github.com/vercel/next.js/blob/canary/examples/blog-starter/lib/api.ts
2 |
3 | import fs from 'fs';
4 | import { join } from 'path';
5 | import matter from 'gray-matter';
6 | import { Post } from '../types';
7 |
8 | const postsDirectory = join(process.cwd(), '_posts');
9 |
10 | export function getPostSlugs(): string[] {
11 | return fs.readdirSync(postsDirectory);
12 | }
13 |
14 | type Fields = (keyof Post)[];
15 |
16 | interface GetPostBySlugArgs {
17 | slug: string;
18 | fields: Fields;
19 | }
20 |
21 | export function getPostBySlug(args: GetPostBySlugArgs): Post {
22 | const { slug, fields = [] } = args;
23 |
24 | const realSlug = slug.replace(/\.md$/, '');
25 | const fullPath = join(postsDirectory, `${realSlug}.md`);
26 | const fileContents = fs.readFileSync(fullPath, 'utf8');
27 | const { data, content } = matter(fileContents);
28 | const dataTyped: Post = data;
29 |
30 | const filteredFields: { [key: string]: unknown } = {};
31 |
32 | // Ensure only the minimal needed data is exposed
33 | fields.forEach((field) => {
34 | if (field === 'slug') {
35 | filteredFields[field] = realSlug;
36 | }
37 | if (field === 'content') {
38 | filteredFields[field] = content;
39 | }
40 |
41 | if (data[field] !== undefined) {
42 | filteredFields[field] = dataTyped[field];
43 | }
44 | });
45 |
46 | return filteredFields;
47 | }
48 |
49 | export function getAllPosts(fields: Fields = []) {
50 | const slugs = getPostSlugs();
51 | const posts = slugs
52 | .map((slug) => getPostBySlug({ slug, fields }))
53 | // sort posts by order in ascending order
54 | .sort((post1, post2) => {
55 | if (post1.order && post2.order) {
56 | return post1.order - post2.order;
57 | }
58 |
59 | return 0;
60 | });
61 | return posts;
62 | }
63 |
--------------------------------------------------------------------------------
/src/components/layout/index.tsx:
--------------------------------------------------------------------------------
1 | import Aside from '../aside';
2 | import SiteFooter from '../siteFooter';
3 | import SiteHeader from '../siteHeader';
4 | import styles from './layout.module.scss';
5 | import { Post } from '../../types';
6 | import CustomHead from '../customHead';
7 | import { useEffect } from 'react';
8 |
9 | interface LayoutProps extends React.PropsWithChildren {
10 | slug: string;
11 | post?: Post;
12 | allPosts?: Post[];
13 | }
14 |
15 | interface GetPageTitleArgs {
16 | title?: string | null;
17 | slug: string;
18 | }
19 |
20 | const getPageTitle = ({ title, slug }: GetPageTitleArgs) => {
21 | if (title) return title;
22 |
23 | return slug === '404' ? 'Страница не найдена' : undefined;
24 | };
25 |
26 | const Layout = ({ children, slug, post, allPosts }: LayoutProps) => {
27 | useEffect(() => {
28 | window.addEventListener('keydown', (event) => {
29 | if (event.code !== 'Tab') return;
30 |
31 | document.body.classList.add('keyboard-navigation');
32 | });
33 | });
34 |
35 | return (
36 |
37 |
38 | {allPosts && (
39 |
43 | )}
44 |
45 |
49 | {children}
50 |
51 | {post && (
52 |
57 | )}
58 |
59 | {allPosts && (
60 |
64 | )}
65 |
66 | );
67 | };
68 |
69 | export default Layout;
70 |
--------------------------------------------------------------------------------
/src/components/icons/index.tsx:
--------------------------------------------------------------------------------
1 | export const TwitterIcon = () => (
2 |
13 | );
14 |
15 | export const GithubIcon = () => (
16 |
27 | );
28 |
--------------------------------------------------------------------------------
/src/components/siteNav/index.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | // import { getAllPosts } from '../../app/utils/api';
3 | import Socials from '../socials';
4 | import styles from './siteNav.module.scss';
5 | import classnames from 'classnames';
6 | import { PageUrl, Post } from '../../types';
7 |
8 | interface SiteNavProps {
9 | slug: string;
10 | ariaLabel: string;
11 | allPosts: Post[];
12 | }
13 |
14 | const sortByOrder = (post1: Post, post2: Post) => {
15 | if (post1.order !== undefined && post2.order !== undefined) {
16 | return post1.order - post2.order;
17 | }
18 |
19 | return 0;
20 | };
21 |
22 | const getPageUrls = (allPosts: Post[]): PageUrl[] => {
23 | const allPostsFiltered = allPosts.filter(({ title, slug }) => title && slug);
24 | allPostsFiltered.sort(sortByOrder);
25 |
26 | return allPostsFiltered.map((item) => {
27 | return {
28 | text: item.title!,
29 | url: item.slug!,
30 | };
31 | });
32 | };
33 |
34 | const getUrl = (url: string) => {
35 | const isHomePageLink = url === 'index';
36 |
37 | return isHomePageLink ? '/' : url;
38 | };
39 |
40 | const SiteNav = ({ slug, ariaLabel, allPosts }: SiteNavProps) => {
41 | const pageUrls = getPageUrls(allPosts);
42 |
43 | return (
44 |
74 | );
75 | };
76 |
77 | export default SiteNav;
78 |
--------------------------------------------------------------------------------
/src/styles/prism.scss:
--------------------------------------------------------------------------------
1 | /* PrismJS 1.29.0
2 | https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript */
3 | code[class*="language-"],
4 | pre[class*="language-"] {
5 | color: #f8f8f2;
6 | background: 0 0;
7 | text-shadow: 0 1px rgba(0, 0, 0, 0.3);
8 | font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
9 | font-size: 1em;
10 | text-align: left;
11 | white-space: pre;
12 | word-spacing: normal;
13 | word-break: normal;
14 | word-wrap: normal;
15 | line-height: 1.5;
16 | -moz-tab-size: 4;
17 | -o-tab-size: 4;
18 | tab-size: 4;
19 | -webkit-hyphens: none;
20 | -moz-hyphens: none;
21 | -ms-hyphens: none;
22 | hyphens: none;
23 | }
24 | pre[class*="language-"] {
25 | padding: 1em;
26 | margin: 0.5em 0;
27 | overflow: auto;
28 | border-radius: 0.3em;
29 | }
30 | :not(pre) > code[class*="language-"],
31 | pre[class*="language-"] {
32 | background: #272822;
33 | }
34 | :not(pre) > code[class*="language-"] {
35 | padding: 0.1em;
36 | border-radius: 0.3em;
37 | white-space: normal;
38 | }
39 | .token.cdata,
40 | .token.comment,
41 | .token.doctype,
42 | .token.prolog {
43 | color: #8292a2;
44 | }
45 | .token.punctuation {
46 | color: #f8f8f2;
47 | }
48 | .token.namespace {
49 | opacity: 0.7;
50 | }
51 | .token.constant,
52 | .token.deleted,
53 | .token.property,
54 | .token.symbol,
55 | .token.tag {
56 | color: #f92672;
57 | }
58 | .token.boolean,
59 | .token.number {
60 | color: #ae81ff;
61 | }
62 | .token.attr-name,
63 | .token.builtin,
64 | .token.char,
65 | .token.inserted,
66 | .token.selector,
67 | .token.string {
68 | color: #a6e22e;
69 | }
70 | .language-css .token.string,
71 | .style .token.string,
72 | .token.entity,
73 | .token.operator,
74 | .token.url,
75 | .token.variable {
76 | color: #f8f8f2;
77 | }
78 | .token.atrule,
79 | .token.attr-value,
80 | .token.class-name,
81 | .token.function {
82 | color: #e6db74;
83 | }
84 | .token.keyword {
85 | color: #66d9ef;
86 | }
87 | .token.important,
88 | .token.regex {
89 | color: #fd971f;
90 | }
91 | .token.bold,
92 | .token.important {
93 | font-weight: 700;
94 | }
95 | .token.italic {
96 | font-style: italic;
97 | }
98 | .token.entity {
99 | cursor: help;
100 | }
101 |
--------------------------------------------------------------------------------
/_posts/catching-bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Поиск ошибок
3 | order: 6
4 |
5 | links:
6 | - text: 'Локализуйте проблему'
7 | url: '#catching-bugs'
8 | - text: 'Используйте цветные обводки'
9 | url: '#outlines'
10 | ---
11 |
12 |
13 | Умение находить ошибки в коде не менее важно, чем умение писать код. И ошибка может оказаться, например, не вашей ошибкой, а особенностью браузера, но в любом случае, прежде чем что-то чинить, нужно как следует разобраться в проблеме.
14 |
15 | Находить ошибки поможет пара простых приёмов.
16 |
17 |
18 |
19 | -
20 |
Локализуйте проблему
21 |
22 | Бывает, что на странице что-то пошло не так, но совершенно непонятно что вызывает эту проблему. Например, появляется горизонтальная прокрутка или одна из колонок падает вниз.
23 |
24 | Дело в каком-то из элементов на странице, но как понять в каком? Для поиска виновника просто удаляйте все элементы по одному, пока не обнаружите нужный. Это удобнее всего делать в веб-инспекторе браузера во вкладке Elements. Выбранный элемент можно удалить нажав Backspace или Delete, а вернуть обратно — нажав Ctrl + Z.
25 |
26 | Ошибки в CSS ищутся аналогично: в том же веб-инспекторе можно включать и выключать правила, пробовать разные комбинации, пока не обнаружится то, что вызывает проблему.
27 |
28 | Также можно делать и в файлах с разметкой и стилями, пряча в комментарии отдельные строки или целые куски кода, можно спрятать хоть все элементы на странице. Когда виновник будет найден, просто вернёте всё на место.
29 |
30 | Главная цель этих действий — найти проблемный элемент, чтобы понять какую часть соответствующего кода вам нужно рассмотреть повнимательней. Не нужно прочесывать весь код — просто скройте то, что точно работает, и сосредоточьтесь на той части, где спряталась ошибка.
31 |
32 |
33 | -
34 |
Используйте цветные обводки
35 |
36 | Чтобы видеть границы элементов и лучше понимать как они взаимодействуют друг с другом, добавьте им цветные обводки (outline). Результат может выглядеть как-то так:
37 |
38 |
43 |
44 | Это позволит лучше представлять себе структуру документа, а ещё так удобно искать причины появления лишних прокруток на странице: может оказаться, что какие-то элементы получились шире, чем вы ожидали — с помощью разноцветных обводок вы легко сможете их обнаружить.
45 |
46 | Аутлайны можно добавлять как в веб-инспекторе браузера, так и в файлы со стилями. Если на странице получилась разноцветная мешанина, обводки, которые временно не нужны, можно закомментировать или просто удалить.
47 |
48 | Аутлайны можно оставлять на всё время разработки, главное перед публикацией финального результата не забыть убрать из кода всё лишнее.
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/_posts/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Введение
3 | order: 0
4 |
5 | links:
6 | - text: 'Используйте HTML-тэги по смыслу'
7 | url: '#sense'
8 | - text: 'Используйте БЭМ для именования классов'
9 | url: '#bem'
10 | - text: 'Используйте в названиях классов простые и короткие слова'
11 | url: '#common-words'
12 | ---
13 |
14 |
15 | Разметка страницы может быть непростым занятием, особенно поначалу. Какой тег выбрать? Какой класс добавить? Какой должна быть разметка, чтобы стили одного элемента не поломали другой?
16 |
17 | На все эти случаи есть простые правила, следуя которым вы сможете писать легко писать чистый, хорошо структурированный HTML-код, который будет удобно читать и приятно поддерживать.
18 |
19 |
20 |
21 | -
22 |
Используйте HTML-тэги по смыслу
23 |
24 |
25 | Элементы для основной раскладки
26 |
27 |
28 | header — шапка страницы или блока.
29 | footer — подвал страницы или блока.
30 | main — главная смысловая часть страницы.
31 | section — разделы внутри основного контента.
32 | article — отдельная статья, пост или комментарий.
33 | nav — навигация, ссылки для перемещения по сайту.
34 | aside — боковая колонка, дополнительный контент не входящий в main.
35 |
36 |
37 | Элементы для содержимого
38 |
39 |
40 | h1-h6 — заголовки. Обычно h1 — это название сайта. Заголовки нужно использовать в порядке иерархии, это важно для доступности.
41 | ul и ol — списки, в них удобно размещать любые перечисляемые элементы.
42 | button — кнопка, например, элемент управления или кнопка для отправки формы.
43 |
44 |
45 | Для элементов без особой смысловой нагрузки можно использовать div или span.
46 |
47 | Для разметки страницы нельзя использовать теги, предназначенные для оформления текста: например, b и i. У них есть собственные стили, которые со временем может понадобиться переопределить или сбросить — проще сразу выбрать элемент, у которого нет стилей по умолчанию. Тег p уместно использовать для блоков текста, но для других случаев лучше выбрать div.
48 |
49 | Это далеко не все теги, которые существуют: вот здесь есть удобный список тегов, сгруппированных по смыслу, с комментариями и примерами кода.
50 |
51 | Не знаете с какой стороны взяться за дело? Начните со статьи Первые шаги.
52 |
53 |
54 |
55 | -
56 |
Используйте БЭМ для именования классов
57 |
58 |
59 | Подробно и с примерами читайте в статье Как писать классы по БЭМ?
60 |
61 |
62 |
63 | -
64 |
Используйте в названиях классов простые и короткие слова
65 |
66 | Вот здесь приведены примеры таких слов для разных случаев.
67 |
68 |
69 |
--------------------------------------------------------------------------------
/.github/workflows/nextjs.yml:
--------------------------------------------------------------------------------
1 | # Sample workflow for building and deploying a Next.js site to GitHub Pages
2 | #
3 | # To get started with Next.js see: https://nextjs.org/docs/getting-started
4 | #
5 | name: Deploy Next.js site to Pages
6 |
7 | on:
8 | # Runs on pushes targeting the default branch
9 | push:
10 | branches: ["master"]
11 |
12 | # Allows you to run this workflow manually from the Actions tab
13 | workflow_dispatch:
14 |
15 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
16 | permissions:
17 | contents: read
18 | pages: write
19 | id-token: write
20 |
21 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
22 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
23 | concurrency:
24 | group: "pages"
25 | cancel-in-progress: false
26 |
27 | jobs:
28 | # Build job
29 | build:
30 | runs-on: ubuntu-latest
31 | steps:
32 | - name: Checkout
33 | uses: actions/checkout@v4
34 | - name: Detect package manager
35 | id: detect-package-manager
36 | run: |
37 | if [ -f "${{ github.workspace }}/yarn.lock" ]; then
38 | echo "manager=yarn" >> $GITHUB_OUTPUT
39 | echo "command=install" >> $GITHUB_OUTPUT
40 | echo "runner=yarn" >> $GITHUB_OUTPUT
41 | exit 0
42 | elif [ -f "${{ github.workspace }}/package.json" ]; then
43 | echo "manager=npm" >> $GITHUB_OUTPUT
44 | echo "command=ci" >> $GITHUB_OUTPUT
45 | echo "runner=npx --no-install" >> $GITHUB_OUTPUT
46 | exit 0
47 | else
48 | echo "Unable to determine package manager"
49 | exit 1
50 | fi
51 | - name: Setup Node
52 | uses: actions/setup-node@v4
53 | with:
54 | node-version: "20"
55 | cache: ${{ steps.detect-package-manager.outputs.manager }}
56 | - name: Setup Pages
57 | uses: actions/configure-pages@v4
58 | with:
59 | # Automatically inject basePath in your Next.js configuration file and disable
60 | # server side image optimization (https://nextjs.org/docs/api-reference/next/image#unoptimized).
61 | #
62 | # You may remove this line if you want to manage the configuration yourself.
63 | static_site_generator: next
64 | - name: Restore cache
65 | uses: actions/cache@v3
66 | with:
67 | path: |
68 | .next/cache
69 | # Generate a new cache whenever packages or source files change.
70 | key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
71 | # If source files changed but packages didn't, rebuild from a prior cache.
72 | restore-keys: |
73 | ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-
74 | - name: Install dependencies
75 | run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
76 | - name: Build with Next.js
77 | run: ${{ steps.detect-package-manager.outputs.runner }} next build
78 | - name: Static HTML export with Next.js
79 | run: ${{ steps.detect-package-manager.outputs.runner }} next export
80 | - name: Upload artifact
81 | uses: actions/upload-pages-artifact@v2
82 | with:
83 | path: ./out
84 |
85 | # Deployment job
86 | deploy:
87 | environment:
88 | name: master
89 | url: ${{ steps.deployment.outputs.page_url }}
90 | runs-on: ubuntu-latest
91 | needs: build
92 | steps:
93 | - name: Deploy to GitHub Pages
94 | id: deployment
95 | uses: actions/deploy-pages@v3
96 |
--------------------------------------------------------------------------------
/src/styles/global.scss:
--------------------------------------------------------------------------------
1 | @import "./themes.scss";
2 |
3 | HTML {
4 | font-size: 12px;
5 | }
6 |
7 | BODY {
8 | margin: 0;
9 | padding: 0;
10 | padding-top: 1rem;
11 | background-color: var(--color-background);
12 | font: 16px/1.5 Trebuchet MS, sans-serif;
13 | color: var(--color-text);
14 | }
15 |
16 | BODY:not(.keyboard-navigation) * {
17 | outline: none;
18 | }
19 |
20 | * {
21 | scroll-behavior: smooth;
22 | }
23 |
24 | /* Links
25 | --------------------------------------------- */
26 |
27 | A {
28 | color: var(--color-link);
29 | text-decoration: underline;
30 | transition: all 0.25s;
31 |
32 | &:visited {
33 | color: var(--color-link-visited);
34 | }
35 |
36 | &:hover {
37 | color: var(--color-link-hover);
38 | text-decoration: none;
39 | }
40 | }
41 |
42 | /* Headers
43 | --------------------------------------------- */
44 |
45 | H1,
46 | H2,
47 | H3,
48 | H4,
49 | H5 {
50 | margin: 0.5em 0;
51 | }
52 |
53 | H2 {
54 | font-size: 1.8rem;
55 |
56 | @media (min-width: 800px) {
57 | font-size: 2.5rem;
58 | }
59 | }
60 |
61 | H3 {
62 | font-size: 1.5rem;
63 |
64 | @media (min-width: 800px) {
65 | font-size: 2rem;
66 | }
67 |
68 | SUP {
69 | font-size: 0.7rem;
70 | opacity: 0.5;
71 |
72 | &:hover {
73 | opacity: 1;
74 | }
75 | }
76 | }
77 |
78 | H4 {
79 | font-size: 1.2rem;
80 |
81 | @media (min-width: 800px) {
82 | font-size: 1.5rem;
83 | }
84 | }
85 |
86 | H5 {
87 | font-size: 1.25rem;
88 | }
89 |
90 | /* Images
91 | --------------------------------------------- */
92 |
93 | FIGURE {
94 | margin: 0;
95 | }
96 |
97 | IMG {
98 | border: 1px solid var(--color-image-border);
99 | max-width: 100%;
100 | height: auto;
101 | }
102 |
103 | /* Lists
104 | --------------------------------------------- */
105 |
106 | OL,
107 | UL {
108 | padding-left: 0;
109 |
110 | @media (min-width: 600px) {
111 | padding-left: 20px;
112 | }
113 | @media (min-width: 800px) {
114 | padding-left: 30px;
115 | }
116 | }
117 |
118 | LI {
119 | margin-bottom: 0.5rem;
120 | }
121 |
122 | DL {
123 | margin-bottom: 2rem;
124 | }
125 |
126 | DT {
127 | font-weight: bold;
128 | }
129 |
130 | LI > DL > DT {
131 | font-size: 1.1em;
132 | }
133 |
134 | DD > DL {
135 | margin-top: 0.5rem;
136 | }
137 |
138 | DD:not(:last-child) {
139 | margin-bottom: 0.5rem;
140 | }
141 |
142 | /* Counters
143 | --------------------------------------------- */
144 |
145 | OL {
146 | counter-reset: list-counter;
147 | list-style: none;
148 | }
149 |
150 | OL OL {
151 | list-style-type: decimal;
152 | padding-left: 2.4em;
153 |
154 | @media (min-width: 600px) {
155 | padding-left: 3em;
156 | }
157 | }
158 |
159 | OL > LI {
160 | counter-increment: list-counter;
161 | }
162 | OL > LI > H2:before {
163 | content: counter(list-counter) ". ";
164 | }
165 |
166 | .list--has-numbers {
167 | list-style-type: decimal;
168 | }
169 |
170 | /* Codes
171 | --------------------------------------------- */
172 |
173 | PRE,
174 | CODE {
175 | border: 1px solid var(--color-code-border);
176 | border-radius: 3px;
177 | background-color: var(--color-code-background);
178 | font-size: 15px;
179 | }
180 |
181 | CODE {
182 | padding: 1px 5px;
183 | }
184 |
185 | PRE {
186 | padding: 8px 12px;
187 | overflow-x: auto;
188 |
189 | > code {
190 | border: 0;
191 | padding-right: 0;
192 | padding-left: 0;
193 | }
194 | }
195 |
196 | .language-css,
197 | .language-html {
198 | font-size: 13px;
199 | }
200 |
201 | /* Tip
202 | --------------------------------------------- */
203 |
204 | .tip {
205 | font-style: italic;
206 | }
207 |
208 | /* Attention
209 | --------------------------------------------- */
210 |
211 | .attention {
212 | padding-left: 1.8rem;
213 | border-left: 5px solid darkorange;
214 | }
215 |
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "at-rule-empty-line-before": [
4 | "always",
5 | {
6 | "except": ["blockless-group", "first-nested"],
7 | "message": "Ожидается пустая строка перед @-правилом"
8 | }
9 | ],
10 | "at-rule-semicolon-newline-after": [
11 | "always",
12 | {
13 | "message": "Каждое @-правило должно быть на новой строке"
14 | }
15 | ],
16 | "block-no-empty": [
17 | true,
18 | {
19 | "message": "Пустых блоков быть не должно"
20 | }
21 | ],
22 | "declaration-colon-space-after": [
23 | "always",
24 | {
25 | "message": "Ожидается пробел после двоеточия"
26 | }
27 | ],
28 | "declaration-colon-space-before": [
29 | "never",
30 | {
31 | "message": "Перед двоеточием не нужен пробел"
32 | }
33 | ],
34 | "declaration-block-no-duplicate-properties": [
35 | true,
36 | {
37 | "message": "Дублирующиеся правила"
38 | }
39 | ],
40 | "declaration-block-no-ignored-properties": [
41 | true,
42 | {
43 | "message": "Это правило не будет работать из-за других правил, заданных для этого элемента"
44 | }
45 | ],
46 | "declaration-block-no-shorthand-property-overrides": [
47 | true,
48 | {
49 | "message": "Сокращенная запись перезапишет стили, заданные выше"
50 | }
51 | ],
52 | "declaration-block-semicolon-newline-after": [
53 | "always",
54 | {
55 | "message": "Нужна новая строка после точки с запятой"
56 | }
57 | ],
58 | "declaration-block-single-line-max-declarations": [
59 | 1,
60 | {
61 | "message": "На строке должно быть только одно правило"
62 | }
63 | ],
64 | "declaration-block-trailing-semicolon": [
65 | "always",
66 | {
67 | "message": "Каждое правило следует заканчивать точкой с запятой"
68 | }
69 | ],
70 | "block-closing-brace-newline-before": [
71 | "always",
72 | {
73 | "message": "Закрывающая скобка должна быть на новой строке"
74 | }
75 | ],
76 | "block-no-single-line": [
77 | true,
78 | {
79 | "message": "Не следует писать блок в одну строку"
80 | }
81 | ],
82 | "block-opening-brace-space-before": [
83 | "always",
84 | {
85 | "message": "Нужен пробел перед открывающей фигурной скобкой"
86 | }
87 | ],
88 | "block-opening-brace-newline-after": [
89 | "always",
90 | {
91 | "message": "Нужен перенос после открывающей фигурной скобки"
92 | }
93 | ],
94 | "declaration-no-important": [
95 | true,
96 | {
97 | "message": "Не следует использовать !important"
98 | }
99 | ],
100 | "indentation": [
101 | 2,
102 | {
103 | "message": "Отступы должны быть кратны 2-м пробелам"
104 | }
105 | ],
106 | "length-zero-no-unit": [
107 | true,
108 | {
109 | "message": "Нулевым значениям можно не указывать единицы измерения"
110 | }
111 | ],
112 | "media-feature-colon-space-after": [
113 | "always",
114 | {
115 | "message": "Ожидается пробел после двоеточия в медиавыражении"
116 | }
117 | ],
118 | "no-duplicate-selectors": [
119 | true,
120 | {
121 | "message": "Не следует дублировать селекторы"
122 | }
123 | ],
124 | "number-leading-zero": [
125 | "always",
126 | {
127 | "message": "В числовом значении перед точкой ожидается ноль"
128 | }
129 | ],
130 | "number-max-precision": [
131 | 3,
132 | {
133 | "message": "В значениях достаточно 3-х знаков после запятой"
134 | }
135 | ],
136 | "rule-nested-empty-line-before": [
137 | "always",
138 | {
139 | "except": ["first-nested"],
140 | "message": "Перед вложенным правилом ожидается пустая строка"
141 | }
142 | ],
143 | "rule-non-nested-empty-line-before": [
144 | "always",
145 | {
146 | "message": "Ожидается пустая строка перед правилом"
147 | }
148 | ],
149 | "selector-pseudo-element-colon-notation": [
150 | "double",
151 | {
152 | "message": "Для псевдоэлементов следует использовать двойное двоеточие"
153 | }
154 | ],
155 | "selector-no-id": [
156 | true,
157 | {
158 | "message": "Для стилизации не следует использовать ID"
159 | }
160 | ],
161 | "selector-list-comma-newline-after": [
162 | "always",
163 | {
164 | "message": "Каждый селектор должен быть на новой строке"
165 | }
166 | ],
167 | "string-quotes": [
168 | "double",
169 | {
170 | "message": "Ожидаются двойные кавычки"
171 | }
172 | ],
173 | "selector-type-no-unknown": [
174 | true,
175 | {
176 | "message": "Такого элемента не существует"
177 | }
178 | ],
179 | "value-no-vendor-prefix": [
180 | true,
181 | {
182 | "message": "Не нужно использовать вендорные префиксы"
183 | }
184 | ],
185 | "unit-no-unknown": [
186 | true,
187 | {
188 | "message": "Некорректные единицы измерения"
189 | }
190 | ]
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/_posts/drop-of-magic.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Капля магии
3 | order: 4
4 |
5 | links:
6 | - text: 'Цель вижу, в себя верю!'
7 | url: '#target'
8 | - text: 'Всем выйти из сумрака!'
9 | url: '#dusk'
10 |
11 | additional_links:
12 | - text: 'Центрирование в CSS: полное руководство'
13 | url: https://css-tricks.com/centering-css-complete-guide/
14 | - text: 'Краш-тест вёрстки'
15 | url: https://isqua.ru/blog/2016/06/19/crash-test-viorstki/
16 | - text: 'Контент по центру, фон по ширине'
17 | url: https://isqua.ru/blog/2016/06/23/content-po-sentru-fon-po-shirinie/
18 |
19 | ---
20 |
21 |
22 | Когда разметка будет готова, к ней потребуется оформление. Если добавить в ваши коды немного вспомогательных стилей, это поможет вам писать CSS проще и быстрее.
23 |
24 |
25 |
26 | -
27 |
Цель вижу, в себя верю!
28 |
29 | Чтобы аккуратно сверстать макет, ничего не потеряв по дороге, нужно или обладать хорошей зрительной памятью, или постоянно сверяться с макетом. Второе отнимает массу времени, и всё равно можно что-то упустить. Для решения проблемы можно просто подложить макет под страницу:
30 |
31 | ```html
32 |
37 | ```
38 |
39 | Код вставляется в элемент <head>.
40 |
41 | Если у вас есть макеты для разных разрешений экрана, их можно менять с помощью медиавыражений:
42 |
43 | ```html
44 |
61 | ```
62 |
63 | Чтобы фоны и картинки на странице не загораживали макеты, можно добавить opacity для <body>:
64 |
65 | ```html
66 |
87 | ```
88 |
89 | Чтобы удобно управлять прозрачностью, можно установить Pixel Glass:
90 |
91 |
92 |
93 | - Установите скрипт в папку с проектом:
94 |
95 | ```js
96 | npm i pixel-glass --save-dev
97 | ```
98 |
99 |
100 | - Добавьте в
<head> подключение файлов скрипта:
101 |
102 | ```html
103 |
104 |
105 | ```
106 |
107 |
108 |
109 |
110 | После этого на странице появится вот такая панель:
111 |
112 |
113 |
114 | Она позволит управлять прозрачностью <body> или выключить её совсем, если она не нужна.
115 |
116 |
117 | Внимание: если локальный сервер смотрит не в корень проекта, а в другую директорию (например, source), при запуске сервера npm-пакеты окажутся снаружи этой директории, и будут недоступны. В этом случае pixel-glass нужно устанавливать в директорию, куда смотрит сервер, склонировав туда пакет с гитхаба:
118 |
119 |
120 | ```js
121 | git clone git@github.com:yoksel/pixel-glass-js.git pixel-glass
122 | ```
123 |
124 | И подключать скрипты и стили оттуда:
125 |
126 | ```html
127 |
128 |
129 | ```
130 |
131 |
132 | Таким образом макеты будут всё время перед глазами и вам не придётся тратить время на переключение в графический редактор и обратно. Особенно это удобно при работе на маленьком экране, когда не получается разместить рядом фотошоп и браузер.
133 |
134 | При использовании этого способа в какой-то момент текст страницы наложится на текст макета, и получится нечитаемая каша. Чтобы отличить их друг от друга, можно CSS-ом раскрасить текст в яркие цвета (например, color: crimson). Получится примерно вот такое:
135 |
136 |
137 |
138 | Сразу видно где страница не совпадает с макетом и что нужно подправить.
139 |
140 | -
141 |
Всем выйти из сумрака!
142 |
143 | Раскладывая элементы по странице, очень удобно видеть где они начинаются и где заканчиваются. Самый простой способ добавить границы выглядит так:
144 |
145 | ```css
146 | .yourclass {
147 | outline: 2px solid deeppink;
148 | }
149 | ```
150 |
151 | Почему outline, а не border? outline не влияет на блочную модель элемента и не меняет его размеры, как это делает border.
152 |
153 | Чтобы добавить обводки сразу нескольким крупным блокам, можно использовать такой код:
154 |
155 | ```css
156 | BODY > * {
157 | outline: 2px solid deeppink;
158 | }
159 | BODY > * > * {
160 | outline: 2px dashed lime;
161 | outline-offset: -2px;
162 | }
163 | BODY > * > * > * {
164 | outline: 2px dotted dodgerblue;
165 | outline-offset: -4px;
166 | }
167 | ```
168 |
169 | В действии это выглядит примерно так:
170 |
171 |
176 |
177 | Этот код тоже можно вставить в head или лучше положить его в файл с стилями, но можно сделать ещё удобнее: добавьте в браузер Stylish (плагин для кастомного CSS) и подключите этот код через него — тогда в любой момент вы сможете включить или выключить обводки не покидая страницу.
178 |
179 | Также можно добавить ещё больше обводок или задать свои цвета (цвета удобно задавать названиями).
180 |
181 |
182 |
--------------------------------------------------------------------------------
/_posts/first-steps.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Первые шаги
3 | order: 1
4 |
5 | links:
6 | - text: 'Cтруктура документа'
7 | url: '#structure'
8 | - text: 'Как правильно выбрать тег?'
9 | url: '#tags'
10 | - text: 'Как выбрать класс?'
11 | url: '#class-names'
12 | - text: 'Чистый код'
13 | url: '#clear-code'
14 |
15 | additional_links:
16 | - url: 'https://youtu.be/atXxkKjPbN8'
17 | text: 'Зачем нужны заголовки — HTML Шорты'
18 | ---
19 |
20 |
21 | Предположим, вам нужно сверстать макет. На первый взгляд он может быть похож на большую каракатицу, к которой
22 | непонятно как подступиться, но если разложить всё по полочкам, справиться будет гораздо проще.
23 |
24 |
25 |
26 | -
27 |
Cтруктура документа
28 |
29 | Смотрите на компоненты страницы как на кубики, из которых вам надо собрать единое целое. Двигайтесь от простого к
30 | сложному, от крупных элементов к мелким. Слона удобнее есть по частям.
31 |
32 | Прежде всего надо разметить основные блоки страницы: шапку, подвал и основное содержимое. Чтобы разметка не
33 | превращалась в кашу из дивов, используйте подходящие HTML-элементы:
34 |
35 |
36 | header — шапка (страницы или блока).
37 | footer — подвал (страницы или блока).
38 | main — главное содержимое страницы.
39 | aside — боковая колонка, дополнительный контент не входящий в main.
40 |
41 |
42 | Получается такая структура:
43 |
44 | ```html
45 |
46 |
47 |
48 |
49 | ```
50 |
51 | Элемента aside может не быть, остальное, как правило, есть.
52 |
53 | Здесь в классах удобно использовать названия элементов, а чтобы было понятно, что это основные блоки страницы,
54 | можно использовать префиксы page-... или site-.... Кроме того, так классы не будут
55 | пересекаться c шапками и подвалами вложенных элементов:
56 |
57 | ```html
58 |
59 |
60 |
61 |
62 | ```
63 |
64 | Теперь эти крупные контейнеры нужно заполнить более мелкими внутренними элементами.
65 |
66 |
67 | -
68 |
Как правильно выбрать тег?
69 |
70 | Это несложно, если знать простые правила:
71 |
72 |
73 | - Это раздел внутри
main?
74 | -
75 | Скорее всего, вам нужен тег
section.
76 |
77 |
78 | - Это пост в блоге, статья или комментарий?
79 | - Используйте
article.
80 |
81 | - Это заголовок?
82 | -
83 | Используйте теги заголовков
h1-h6 соответствующего уровня. Обычно h1 — это название
84 | сайта, и заголовок такого уровня должен быть на странице один. Все последующие заголовки должны задаваться в
85 | иерархической последовательности. Например, заголовок текущей страницы — h2, заголовок раздела на
86 | странице — h3, а заголовок подраздела уровнем ниже — h4.
87 |
88 |
89 | - Это навигация или меню?
90 | - Используйте тег
nav. Внутри могут находиться просто ссылки либо ссылки внутри списка — зависит от
91 | вашего макета.
92 |
93 | - Это несколько однотипных элементов?
94 | - Скорее всего, вам нужно сгруппировать их с помощью
ul или ol.
95 |
96 | - Этот элемент можно кликнуть?
97 | -
98 |
99 | - По клику происходит действие на странице либо отправка формы?
100 | - Используйте
button.
101 |
102 | - По клику происходит переход на другую страницу?
103 | - Это простая ссылка,
a.
104 |
105 |
106 |
107 |
108 | - Это картинка?
109 |
110 | -
111 |
112 | - Это иллюстрация в статье, товар в каталоге магазина или картинка в фотогалерее?
113 | - Это изображение относится к содержимому страницы, вам нужен
img, обязательно укажите текст в
114 | alt="".
115 |
116 | - Это иконка?
117 | - Иконку можно вставить псевдоэлементами либо инлайновым
svg. Псевдоэлементы помогут сохранить
118 | более чистый код. Если иконка задаётся кнопке, до применения стилей в кнопке должен быть текст.
119 |
120 | - Если эту картинку убрать, смысл блока не потеряется?
121 | - Это изображение лучше добавить с помощью CSS.
122 |
123 |
124 |
125 | - Это ярлык для текстового поля?
126 | - Используйте
label. Чтобы связать лейбл с полем, поместите инпут в лейбл либо свяжите их атрибутом
127 | for="".
128 |
129 | - Это абзац в тексте?
130 | - Используйте
p.
131 |
132 | - Ничего не подошло?
133 | - Смело используйте
div или span.
134 |
135 |
136 |
137 | Если вы на этом шаге всё сделали правильно, открыв вашу страницу в браузере, вы увидите хорошо структурированный
138 | документ с чётко прослеживаемой иерархией, причём ещё до подключения стилей к странице.
139 |
140 | Посмотреть структуру страницы в виде дерева можно здесь: yoksel.github.io/html-tree/.
142 |
143 |
144 | -
145 |
Как выбрать класс?
146 |
147 | Для многих элементов страницы уже есть устоявшиеся названия классов, и вы можете просто выбрать подходящий класс
148 | в этом справочнике.
149 |
150 | Также вам может пригодиться статья Как писать классы по БЭМ?
151 |
152 |
153 | -
154 |
Чистый код
155 |
156 | Очень важно поддерживать код в порядке: это поможет, в первую очередь, вам самим не заблудиться в коде, когда он
157 | станет слишком большим. Для этого:
158 |
159 |
160 | - Разделяйте блоки переносами
161 | - Чтобы было видно где закончился один блок и начался другой.
162 |
163 | - Отступы в коде должны отражать вложенность элементов друг в друга
164 | - Чтобы лучше видеть структуру документа.
165 |
166 | - Сохраняйте единообразие в коде
167 | - Все схожие элементы должны быть размечены в едином стиле и использовать однотипные классы. Например, нельзя
168 | использовать для картинок одновременно
.picture и .img — выберите что-то одно.
169 |
170 |
171 |
172 |
--------------------------------------------------------------------------------
/_posts/check-code.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Тестируем всё
3 | order: 5
4 |
5 | links:
6 | - text: 'Проверить HTML'
7 | url: '#check-html'
8 | - text: 'Проверить БЭМ-разметку'
9 | url: '#check-bem'
10 | - text: 'Проверить CSS'
11 | url: '#check-css'
12 | - text: 'Проверить страницу на разных размерах экрана'
13 | url: '#devices'
14 | - text: 'Проверить страницу на соответствие макету'
15 | url: '#check-maket'
16 | - text: 'Проверить шрифты'
17 | url: '#check-fonts'
18 | - text: 'Проверить изображения'
19 | url: '#check-images'
20 | - text: 'Проверить на переполнение контентом'
21 | url: '#check-overflow'
22 |
23 | ---
24 |
25 |
26 | Как проверить, что вы всё сделали правильно? Что в разметке и в стилях нет ошибок и что результат соответствует макету? Можно воспользоваться разными инструментами, каждый для своих целей.
27 |
28 |
29 |
30 | -
31 |
Проверить HTML
32 |
33 | Используйте валидатор разметки.
34 |
35 | Он проверит ваш HTML на соответствие спецификациям, а так же поможет найти простые ошибки, вроде незакрытых тегов.
36 |
37 | Подсказка: если валидатор говорит, что в section и article обязательно должны быть заголовки, добавьте их в разметку. Если в макете нет текста, подходящего по смыслу, можно сделать скрытые заголовки используя способ с классом .visuallyhidden, это важно для доступности.
38 |
39 |
40 | -
41 |
Проверить БЭМ-разметку
42 |
43 | Используйте Html tree.
44 |
45 | Инструмент построит структуру страницы, проверит БЭМ-разметку на самые простые ошибки и покажет иерархию заголовков.
46 |
47 |
48 | -
49 |
Проверить CSS
50 |
51 | Валидация
52 |
53 | Используйте CSS syntax validator.
54 |
55 | Форматирование
56 |
57 | Используйте The stylelint CLI.
58 |
59 | Установка:
60 |
61 | ```js
62 | npm install -g stylelint
63 | ```
64 |
65 | Также нужен файл с правилами проверки стилей. Можно взять мой набор правил, или накликать свой используя Stylelint Config Generator.
66 |
67 | Список правил сохраните в корне проекта в файл с названием .stylelintrc (расширение не нужно).
68 |
69 | Файл с правилами можно редактировать под ваши нужды, узнать больше о правилах можно здесь.
70 |
71 | Использование:
72 |
73 | SASS:
74 | ```js
75 | stylelint "sass/**/*.scss"
76 | ```
77 |
78 | LESS:
79 | ```js
80 | stylelint "less/**/*.less"
81 | ```
82 |
83 | Команда запускается в папке проекта.
84 |
85 |
86 |
87 | -
88 |
Проверить страницу на разных размерах экрана
89 |
90 | Используйте эмулятор мобильных устройств, который есть в Хроме. Кнопка включения находится в панели разработчиков, вторая иконка в верхнем ряду:
91 |
92 |
93 |
94 | В эмуляторе в выпадающем меню можно просто выбрать устройство с подходящими размерами, а можно кликнуть «Edit» и добавить свои:
95 |
96 |
97 |
98 | Справа на скриншоте есть выпадушка с выбором типа устройства (на скриншоте Mobile), эта опция влияет на наличие прокрутки на странице. Чтобы прокрутка не отъедала ширину страницы, между Mobile и Desktop всегда выбирайте Mobile.
99 |
100 | Там же можно скрыть устройства, которые вам не нужны.
101 |
102 | Мой список устройств выглядит так:
103 |
104 |
105 |
106 | Такой поход избавляет от необходимости подбирать размер окна руками, а так же позволяет быстро переключаться между вьюпортам, причём именно теми, которые нужны вам.
107 |
108 | Не забывайте также проверять макеты на очень широких экранах (1400+), чтобы убедиться что и в этом случае страница не разваливается.
109 |
110 |
111 | -
112 |
Проверить страницу на соответствие макету
113 |
114 | Используйте Pixel Glass или расширение для Хрома PerfectPixel.
115 |
116 | Pixel Glass больше подходит для работы над проектом с адаптивной вёрсткой (макеты будут меняться сами при изменении размеров окна), PerfectPixel — для быстрой проверки страниц.
117 |
118 |
119 | -
120 |
Проверить шрифты
121 |
122 | Панель разработчика → Network → Fonts.
123 |
124 | Подсказка: проверьте, что в браузерах с поддержкой woff2 загрузится именно этот формат. Если грузится woff, проверьте порядок перечисления шрифтов. Браузер выбирает первый подходящий, а не оптимальный из перечисленных.
125 |
126 |
127 | -
128 |
Проверить изображения
129 |
130 | Панель разработчика → Network → Img.
131 |
132 | Плотность пикселей устройства можно выбрать в меню эмулятора:
133 |
134 |
135 |
136 | Если там нет такой опции, нажмите на три точки в правой части панели устройств и включите её в выпадающем меню:
137 |
138 |
139 |
140 | Если все адаптивные изображения содержат в своём адресе @, этот спецсимвол можно использовать для фильтрации, чтобы в панели показывались только те картинки, которые нужно проверить:
141 |
142 |
143 |
144 | В панели устройств меняйте размеры экрана и плотность пикселей и смотрите какие изображения загрузились.
145 |
146 |
147 | -
148 |
Проверить страницу на переполнение контентом
149 |
150 | Введите в консоль браузера команду:
151 |
152 | ```js
153 | document.body.contentEditable = true
154 | ```
155 |
156 | После этого вы сможете отредактировать любой текстовый элемент на странице и добавить в него текст. Это позволит проверить поведение страницы в случаях когда контента слишком мало или наоборот — слишком много.
157 |
158 | При добавлении текста элементы должны тянуться по вертикали, текст не должен упираться в края элемента или обрезаться.
159 |
160 | Статья по теме: Краш-тест вёрстки.
161 |
162 |
163 |
--------------------------------------------------------------------------------
/_posts/bem-rules.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Как писать классы по БЭМ?
3 | order: 2
4 |
5 | links:
6 | - text: 'Простой пример: Блок + Элемент'
7 | url: '#block-elem'
8 | - text: 'Пример посложнее: Блок + Элемент + Модификатор'
9 | url: '#block-elem-mod'
10 | - text: 'Ещё сложнее: что делать, если хочется сделать элемент элемента?'
11 | url: '#elem-elem'
12 | ---
13 |
14 |
15 | БЭМ расшифровывается как «Блок Элемент Модификатор». На самом деле, это целый стэк технологий, из которого мы воспользуемся только соглашением по именованию классов.
16 |
17 | Почему БЭМ?
18 |
19 |
20 | - БЭМ позволяет создавать абсолютно независимые блоки. Блоки и элементы получают уникальные имена, так что стили для одного элемента ничего не поломают в другом.
21 | - БЭМ помогает легко придумывать любое количество классов, не повторяющихся между собой.
22 | - БЭМ помогает писать самодокументирующийся код, в классе любого элемента содержится информация о нём.
23 |
24 |
25 | Подробнее можно почитать в разделах Быстрый старт и
26 | Часто задаваемые вопросы на сайте bem.info.
27 |
28 | Ниже показаны примеры кода.
29 |
30 |
31 |
32 | -
33 |
Простой пример: Блок + Элемент
34 |
35 | Допустим, у вас есть блок с заголовком, текстом и кнопкой внутри, например, это всплывающее окно — попап. Разметка:
36 |
37 | ```html
38 |
39 | Заголовок
40 | Текст
41 |
42 |
43 | ```
44 |
45 | Добавляем класс содержащий назначение элемента: .popup:
46 |
47 | ```html
48 |
49 | Заголовок
50 | Текст
51 |
52 |
53 | ```
54 |
55 | Теперь попробуем добавить классы вложенным элементам:
56 |
57 | ```html
58 |
59 | Заголовок
60 | Текст
61 |
62 |
63 | ```
64 |
65 | Классы удобные, но не уникальные. Если на странице будут ещё элементы с классами .title и .text, их стили могут затронуть элементы в попапе. Селектор типа .popup .title может в будущем создать проблемы со специфичностью. Можно придумать другие классы, но чем больше похожих по смыслу элементов, тем сложнее придумывать новые классы.
66 |
67 | А теперь применим БЭМ-нотацию: каждому элементу внутри блока добавим префикс с классом родителя, например, для заголовка это будет popup__title:
68 |
69 | ```html
70 |
71 | Заголовок
72 | Текст
73 |
74 |
75 | ```
76 |
77 | Теперь эти классы легко решают сразу две задачи: во-первых, благодаря уникальным классам стили для них никогда не пересекутся с другими подобными элементами на странице, а во-вторых, по таким классам сразу видно, что это элементы блока .popup.
78 |
79 |
80 | -
81 |
Пример посложнее: Блок + Элемент + Модификатор
82 |
83 | Для примера возьмём сервисное сообщение на сайте. Обычно такие сообщения бывают разных видов, например, сообщение об успешном завершении действия или об ошибке.
84 |
85 | ```html
86 |
90 | ```
91 |
92 | Логично использовать одну и ту же разметку, но с разными цветовыми темами. Именно здесь очень пригодятся модификаторы.
93 |
94 | ```html
95 |
99 |
100 |
104 | ```
105 |
106 | Обоим элементам можно добавить одинаковые стили используя общий класс .message и так же легко можно добавить отдельные стили для каждого из них, используя уникальный класс с модификатором:
107 |
108 | ```css
109 | .message {
110 | border: 1px solid gray;
111 | }
112 |
113 | .message--success {
114 | border-color: green;
115 | }
116 |
117 | .message--error {
118 | border-color: red;
119 | }
120 | ```
121 |
122 | Оба сообщения будут иметь рамку толщиной один пиксель, но для сообщения об успешной операции она будет зелёной, а для сообщения об ошибке — красной.
123 |
124 |
125 | -
126 |
Ещё сложнее: что делать, если хочется сделать элемент элемента?
127 |
128 | Например, на странице есть блок новостей:
129 |
130 | ```html
131 |
132 | Новости
133 |
134 |
135 |
136 |
137 |
138 |
139 | ```
140 |
141 | Заголовок блока логично получает класс .news__title, список — .news__list, а отдельная новость — .news__item:
142 |
143 | ```html
144 |
145 | Новости
146 |
147 |
148 |
149 |
150 |
151 |
152 | ```
153 |
154 | Тут никаких проблем возникнуть не должно. Теперь добавим разметку отдельной новости:
155 |
156 | ```html
157 |
158 | Новости
159 |
160 |
161 | -
162 |
Заголовок новости
163 | Текст новости
164 |
165 |
166 |
167 |
168 | ```
169 |
170 | Нам нужно добавить класс заголовку новости. Первым делом приходит в голову .news__title, но такой класс уже занят. Предположим, что второй элемент будет не .title, а .subject, тогда в CSS получается такое:
171 |
172 | ```css
173 | .news__title { ... }
174 | .news__subject { ... }
175 | ```
176 |
177 | Без дополнительных комментариев будет совершенно невозможно понять какой из них является заголовком всего блока, а какой — отдельной новости. Не пойдёт.
178 |
179 | Следующий вариант — .news__item__title, но в БЭМ нельзя создавать элемент элемента, и это понятно, потому что получается каша. Ещё вариант: .news__item-title — тоже не годится, потому что может быть неочевидным как title соотносится с item. Как же быть?
180 |
181 | Решение простое: на уровне элемента .news__item можно объявить новый блок (например, .news-item), и строить вложенные классы уже от него. Да, это не самостоятельный переиспользуемый блок, здесь объявление блока нужно только для того, чтобы разгрузить селекторы. Что получается:
182 |
183 | ```html
184 |
185 | Новости
186 |
187 |
188 | -
189 |
Заголовок новости
190 | Текст новости
191 |
192 |
193 |
194 |
195 | ```
196 |
197 | Проблема решена: нам больше не нужно использовать монструозные классы, при этом класс точно описывает элемент, и в CSS будет сразу понятно какой класс за что отвечает:
198 |
199 | ```css
200 | .news__title { ... }
201 | .news-item__title { ... }
202 | ```
203 |
204 | Простой и удобный выход из неудобной ситуации.
205 |
206 |
207 |
208 |
209 | Больше примеров разметки можно увидеть здесь.
210 |
211 | Ещё одно хорошее руководство по использованию БЭМ есть здесь.
212 |
--------------------------------------------------------------------------------
/_posts/accessibility.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Доступность
3 | order: 7
4 |
5 | links:
6 | - url: '#accessibility-important'
7 | text: 'Доступность — это важно'
8 | - url: '#accessibility-simple'
9 | text: 'Доступность — это просто'
10 | - url: '#examples'
11 | text: 'Покажите мне всё'
12 |
13 | additional_links:
14 | - url: 'https://youtu.be/atXxkKjPbN8'
15 | text: 'Зачем нужны заголовки — HTML Шорты'
16 | - url: 'https://youtu.be/Ns0zijQJxH4'
17 | text: 'Как прятать — HTML Шорты'
18 | - url: 'http://css.yoksel.ru/inaccessibility'
19 | text: 'Недоступность в картинках: как скринридеры видят сайты'
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 |
Если нужно сделать кликабельный элемент, выбирайте ссылку или кнопку. Выбирайте ссылку, если клик уводит на другую страницу, если нет — используйте кнопку. Скринридеры понимают эти элементы как активные, и могут озвучить это для пользователя. Если вместо кнопки используется div или span, скринридер не поймёт, что на него можно нажать.
50 |
51 | Также, по возможности, делайте кликабельную область большого размера, даже если элемент визуально небольшой. Это особенно важно для мобильных версий, где мы кликаем пальцем, но и на широких экранах будет удобнее, если по кнопке или ссылке можно попасть не прицеливаясь.
52 |
53 |
54 |
По теме:
55 |
56 | -
57 | Доклад Вадима Макеева «Жми сюда!»
58 |
59 | -
60 | Доклад Leonie Watson «Introduction to accessibility mechanics»
61 |
62 |
63 |
64 |
65 |
66 | Заголовки
67 | -
68 |
Теги заголовков h1-h6 нужны не только для красоты, но и для выстраивания структуры страницы, с их помощью можно сформировать иерархическое дерево документа с разделами и подразделами.
69 |
70 | Выбор уровня заголовка на основе иерархии документа решает сразу две задачи:
71 |
72 |
73 | - Как верстальщику, вам не придётся ломать голову над тегом для заголовка: у соседних элементов уровень заголовков одинаковый, если у родителя заголовок
h2, то у дочерних элементов должны быть заголовки h3, и так далее.
74 |
75 | - Основываясь на заголовках скринридеры строят структуру страницы, по которой можно навигироваться, таким образом пользователи читалок могут сразу выбрать нужный раздел без необходимости читать весь текст — это как быстро найти нужную главу в оглавлении книги.
76 |
77 |
78 | Иногда бывает, что на странице есть какой-то самостоятельный раздел, но по макету у него нет заголовка. Получается, что эта часть страницы не будет представлена в оглавлении, которым пользуются читалки. Проблему можно решить добавив заголовок, который затем будет скрыт с помощью CSS. Скрывать рекомендуется таким кодом (источник):
79 |
80 | ```css
81 | .visuallyhidden:not(:focus):not(:active) {
82 | position: absolute;
83 | width: 1px;
84 | height: 1px;
85 | margin: -1px;
86 | border: 0;
87 | padding: 0;
88 | white-space: nowrap;
89 | clip-path: inset(100%);
90 | clip: rect(0 0 0 0);
91 | overflow: hidden;
92 | }
93 | ```
94 |
95 | Такого заголовка не будет на странице, но скринридеры прочитают его без труда. Также это решает задачу с тем, что по спецификации у section и article должны быть заголовки: если они не предусмотрены по макету, просто добавьте скрытые.
96 |
97 |
98 |
По теме:
99 |
100 | -
101 | Валидатор разметки, для отображения структуры заголовков включите галку «outline»
102 |
103 | -
104 | Генератор HTML-дерева — построит дерево документа и структуру заголовков
105 |
106 |
107 |
108 |
109 |
110 | Изображения
111 |
112 | -
113 |
У всех img на странице должен быть указан атрибут alt: его могут прочитать скринридеры, его увидят пользователи с отключенными или незагрузившимися картинками.
114 |
115 | Если в качестве контентного изображения используется инлайновый SVG, его содержимое можно сделать доступным для скринридеров, добавив role="img" aria-label="Описание картинки".
116 |
117 | Если картинки используются как фон для текста, задайте таким блокам фоновый цвет, схожий с цветами фонового изображения. Если картинки не загрузятся (или отключены), текст окажется на констрастном фоне и его всё равно можно будет прочитать.
118 |
119 |
120 |
По теме:
121 |
122 | -
123 | Web Accessibility Tutorials: Images Concepts
124 |
125 | -
126 | Статья Ire Aderinokun «Alternative Text and Images», перевод
127 |
128 | -
129 | Статья «Логотип не отвечает или временно недоступен»
130 |
131 |
132 |
133 |
134 |
135 | Элементы формы
136 |
137 | -
138 |
Для разметки формы обязательно используйте соответствующие элементы, например fieldset, legend, label, input и textarea.
139 |
140 | Скринридеры понимают такие элементы. Они видят fieldset как группу инпутов, а legend — как название группы. Скринридер прочитает лейблы как названия для чекбоксов и полей, и пользователь сможет выбрать желаемые опции или ввести текст.
141 |
142 | Без лейблов скринридер не сможет понять назначение инпутов, а без fieldset и legend — понять как группируются элементы формы и как они связаны между собой, и форма может оказаться полностью недоступной.
143 |
144 |
145 |
146 | Как видите, здесь нет ничего сложного, и просто используя теги по смыслу, можно получить не только аккуратный осмысленный код, но и сделать страницу немного лучше с точки зрения доступности.
147 |
148 |
152 |
--------------------------------------------------------------------------------
/_posts/examples.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Примеры кода
3 | order: 3
4 |
5 | links:
6 | - text: 'Простой список'
7 | url: '#simple-list'
8 | - text: 'Картинка пользователя (юзерпик)'
9 | url: '#userpic'
10 | - text: 'Галерея'
11 | url: '#gallery'
12 | - text: 'Навигация (простой вариант)'
13 | url: '#nav-1'
14 | - text: 'Навигация (сложный вариант)'
15 | url: '#nav-2'
16 | - text: 'Виджет в боковой колонке'
17 | url: '#widget'
18 | - text: 'Блок новостей'
19 | url: '#news'
20 | - text: 'Статья или пост в блоге (простой вариант)'
21 | url: '#simple-article'
22 | - text: 'Статья или пост в блоге (сложный вариант)'
23 | url: '#article'
24 | - text: 'Разметка страницы'
25 | url: '#page-markup'
26 | ---
27 |
28 |
29 | Это примеры блоков, размеченных по описанным выше принципам, от простого к сложному.
30 |
31 |
32 |
33 | -
34 |
Простой список
35 |
36 | ```html
37 |
38 | - Первое
39 | - Второе
40 | - Третье
41 |
42 | ```
43 |
44 |
45 | -
46 |
Картинка пользователя (юзерпик)
47 |
48 | ```html
49 |
50 |
51 | Дормидонт Петрович
52 |
53 | ```
54 |
55 |
56 | -
57 |
Галерея
58 |
59 | ```html
60 |
70 | ```
71 |
72 |
73 | -
74 |
Навигация (простой вариант)
75 |
76 | ```html
77 |
84 | ```
85 |
86 |
87 | -
88 |
Навигация (сложный вариант)
89 |
90 | ```html
91 |
107 | ```
108 |
109 |
110 | -
111 |
Виджет в боковой колонке
112 |
113 | ```html
114 |
125 | ```
126 |
127 |
128 | -
129 |
Блок новостей
130 |
131 | ```html
132 |
133 | Вчерашние новости
134 |
135 |
136 |
138 | -
139 |
140 | Соревнования среди воблы по конькобежному спорту
141 |
142 |
143 | Победила команда килек из Петрозаводска
144 |
145 | Читать дальше
146 |
147 |
148 |
149 | -
150 |
151 | Учёные уточнили роль напильника в уходе за ногтями
152 |
153 |
154 | Британские учёные высоко оценили вклад
155 | напильника в отращивание полутораметровых ногтей.
156 |
157 | Не читать дальше
158 |
159 |
160 |
161 |
162 | ```
163 |
164 |
165 | -
166 |
Статья или пост в блоге (простой вариант)
167 |
168 | ```html
169 |
170 |
171 | Нащупываем чакры у пучка петрушки
172 |
173 |
174 |
175 |
180 |
181 |
182 | Сходите на рынок и купите у старушек пучок петрушки грамм на 100.
183 | Как следует переберите, очистите от жуков и гусениц. Жуков отдайте поиграться
184 | коту, гусениц поселите в горшок с кактусами, пусть одна будет Джоном,
185 | вторая Билли, а у вас в горшке теперь будет Дикий Запад. Вернитесь
186 | к пучку петрушки. Ласково взгляните на него и как следует почешите
187 | за ухом, можно себе или коту. Перевяжите атласной ленточкой,
188 | непременно завяжите бант. Поздравляем! Теперь у вас есть полностью
189 | одомашненный пучок петрушки, который будет весело бегать за вами
190 | по пятам и проращивать свои семена в ваших тапках.
191 |
192 |
193 | ```
194 |
195 |
196 | -
197 |
Статья или пост в блоге (сложный вариант)
198 |
199 | ```html
200 |
201 |
202 |
203 |
204 | Резиновые уточки как способ самопознания
205 |
206 |
207 |
208 |
209 |
210 |
211 |
216 |
217 |
218 | Достаньте с чердака коробку с полусотней резиновых уточек,
219 | оставшихся после празднования нового года. Из уточек
220 | и горящих свечей выложите пентаграмму на полу комнаты.
221 | Сядьте посередине в позу лотоса, в каждую руку возьмите
222 | по немецко-бразильскому словарю, прокашляйтесь, наберите
223 | полную грудь воздуха и громко и уверенно,
224 | с полной самоотдачей скажите "Кря!"
225 |
226 |
227 |
242 |
243 |
254 |
255 | ```
256 |
257 |
258 | -
259 |
Разметка страницы
260 |
261 | ```html
262 |
263 |
264 | Название сайта
265 |
266 |
271 |
272 |
273 |
274 |
275 |
276 |
277 | Преимущества
278 |
279 |
280 | - Высокие потолки
281 | [...]
282 |
283 |
284 |
285 |
286 | Тарифы
287 |
288 |
289 | - 30 зонтиков в минуту
290 | [...]
291 |
292 |
293 |
294 |
295 |
296 |
302 |
303 |
304 |
315 | ```
316 |
317 |
318 |
--------------------------------------------------------------------------------