├── README.md ├── .gitignore ├── _includes ├── footer.html ├── link-github.html ├── link-twitter.html ├── header.html ├── nav.html ├── links-list.html ├── page-nav.html ├── icon-twitter.svg ├── head.html └── icon-github.svg ├── _posts └── 2016-06-03-hello.markdown ├── assets ├── img │ ├── font-styles │ │ ├── after.png │ │ ├── before.png │ │ ├── check.png │ │ ├── input.png │ │ ├── input-fixed.png │ │ ├── mediaquery.png │ │ ├── input-computed.png │ │ ├── check-custom-font.png │ │ └── input-fixed-expanded.png │ ├── mediaqueries │ │ └── before.png │ ├── blocks-with-text │ │ ├── after.png │ │ ├── before.png │ │ ├── content-abs-before.png │ │ ├── content-abs-after-long.png │ │ └── content-abs-after-short.png │ ├── containers │ │ ├── before-body.png │ │ ├── before-desktop.png │ │ ├── before-mobile.png │ │ ├── bg-fullwide-example.png │ │ ├── img-fullwide-example.png │ │ ├── img-fullwide-scroll.png │ │ └── img-fullwide-no-scroll.png │ └── forms-markup │ │ ├── textarea-after.png │ │ └── textarea-in-fieldset.png ├── css │ ├── main.scss │ └── prism.css └── js │ └── prism.js ├── _data └── tags.yml ├── .editorconfig ├── _layouts ├── page.html ├── post.html └── default.html ├── _sass ├── _vars-mixins.scss ├── _footnotes.scss ├── _page-nav.scss ├── _site-nav.scss ├── button-example.scss ├── _post.scss ├── _socials.scss ├── _compare.scss ├── _layout.scss ├── _form-example.scss └── _base.scss ├── index.html ├── feed.xml ├── _config.yml ├── styles-in-mediaquery └── index.html ├── .stylelintrc ├── font-styles └── index.html ├── blocks-with-text └── index.html ├── containers └── index.html └── forms-markup └── index.html /README.md: -------------------------------------------------------------------------------- 1 | # HTML & CSS: как не надо 2 | 3 | https://yoksel.github.io/bad-practices/ 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | npm-debug.log 3 | .sass-cache 4 | .jekyll-metadata 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /_includes/footer.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /_posts/2016-06-03-hello.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Hello" 4 | tags: [onetag] 5 | --- 6 | 7 | Some text -------------------------------------------------------------------------------- /assets/img/font-styles/after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/font-styles/after.png -------------------------------------------------------------------------------- /assets/img/font-styles/before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/font-styles/before.png -------------------------------------------------------------------------------- /assets/img/font-styles/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/font-styles/check.png -------------------------------------------------------------------------------- /assets/img/font-styles/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/font-styles/input.png -------------------------------------------------------------------------------- /assets/img/mediaqueries/before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/mediaqueries/before.png -------------------------------------------------------------------------------- /assets/img/blocks-with-text/after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/blocks-with-text/after.png -------------------------------------------------------------------------------- /assets/img/blocks-with-text/before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/blocks-with-text/before.png -------------------------------------------------------------------------------- /assets/img/containers/before-body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/containers/before-body.png -------------------------------------------------------------------------------- /assets/img/font-styles/input-fixed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/font-styles/input-fixed.png -------------------------------------------------------------------------------- /assets/img/font-styles/mediaquery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/font-styles/mediaquery.png -------------------------------------------------------------------------------- /assets/img/containers/before-desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/containers/before-desktop.png -------------------------------------------------------------------------------- /assets/img/containers/before-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/containers/before-mobile.png -------------------------------------------------------------------------------- /assets/img/font-styles/input-computed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/font-styles/input-computed.png -------------------------------------------------------------------------------- /assets/img/forms-markup/textarea-after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/forms-markup/textarea-after.png -------------------------------------------------------------------------------- /assets/img/containers/bg-fullwide-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/containers/bg-fullwide-example.png -------------------------------------------------------------------------------- /assets/img/containers/img-fullwide-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/containers/img-fullwide-example.png -------------------------------------------------------------------------------- /assets/img/containers/img-fullwide-scroll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/containers/img-fullwide-scroll.png -------------------------------------------------------------------------------- /assets/img/font-styles/check-custom-font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/font-styles/check-custom-font.png -------------------------------------------------------------------------------- /assets/img/containers/img-fullwide-no-scroll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/containers/img-fullwide-no-scroll.png -------------------------------------------------------------------------------- /assets/img/font-styles/input-fixed-expanded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/font-styles/input-fixed-expanded.png -------------------------------------------------------------------------------- /assets/img/forms-markup/textarea-in-fieldset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/forms-markup/textarea-in-fieldset.png -------------------------------------------------------------------------------- /assets/img/blocks-with-text/content-abs-before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/blocks-with-text/content-abs-before.png -------------------------------------------------------------------------------- /_data/tags.yml: -------------------------------------------------------------------------------- 1 | ## Картинки 2 | 3 | - slug: img 4 | desc: картинка 5 | 6 | - slug: icon 7 | desc: иконка 8 | 9 | - slug: logo 10 | desc: логотип 11 | -------------------------------------------------------------------------------- /assets/img/blocks-with-text/content-abs-after-long.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/blocks-with-text/content-abs-after-long.png -------------------------------------------------------------------------------- /assets/img/blocks-with-text/content-abs-after-short.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoksel/bad-practices/HEAD/assets/img/blocks-with-text/content-abs-after-short.png -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /_layouts/page.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 |
5 |

{{ page.title }}

6 | 7 |
8 | {{ content }} 9 |
10 |
11 | -------------------------------------------------------------------------------- /_includes/link-github.html: -------------------------------------------------------------------------------- 1 | {% include icon-github.svg %}{{ include.username }} 2 | -------------------------------------------------------------------------------- /_includes/link-twitter.html: -------------------------------------------------------------------------------- 1 | {% include icon-twitter.svg %}{{ include.username }} 3 | -------------------------------------------------------------------------------- /_sass/_vars-mixins.scss: -------------------------------------------------------------------------------- 1 | $text-color: #222; 2 | $background-color: #FFF; 3 | $link-color: #2a7ae2; 4 | 5 | @mixin clear { 6 | clear: both; 7 | 8 | &:before, 9 | &:after { 10 | content: ""; 11 | display: table; 12 | width: 100%; 13 | } 14 | } -------------------------------------------------------------------------------- /_includes/header.html: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /_sass/_footnotes.scss: -------------------------------------------------------------------------------- 1 | .footnotes { 2 | margin: 2rem 0 0; 3 | padding-top: 1rem; 4 | border-top: 1px solid #DDD; 5 | list-style-type: decimal; 6 | font-size: .9em; 7 | color: #777; 8 | } 9 | 10 | .footnotes__item { 11 | float: left; 12 | clear: both; 13 | padding: .3rem .5rem; 14 | } 15 | 16 | .footnotes__item:target { 17 | background: lemonchiffon; 18 | } 19 | -------------------------------------------------------------------------------- /_includes/nav.html: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /assets/css/main.scss: -------------------------------------------------------------------------------- 1 | --- 2 | # Only the main Sass file needs front matter (the dashes are enough) 3 | --- 4 | @charset "utf-8"; 5 | 6 | // Import partials from `sass_dir` (defaults to `_sass`) 7 | @import 8 | "vars-mixins", 9 | "base", 10 | "layout", 11 | "page-nav", 12 | "footnotes", 13 | "socials", 14 | "site-nav", 15 | "compare", 16 | "post", 17 | "form-example", 18 | "button-example" 19 | ; 20 | -------------------------------------------------------------------------------- /_sass/_page-nav.scss: -------------------------------------------------------------------------------- 1 | .page-nav { 2 | @media (min-width: 1000px) { 3 | margin-bottom: 2rem; 4 | } 5 | } 6 | 7 | .page-nav__title { 8 | font-weight: bold; 9 | font-size: 1.25rem; 10 | } 11 | .page-nav__list { 12 | font-size: .9em; 13 | } 14 | OL.page-nav__list { 15 | list-style-type: decimal; 16 | } 17 | .page-nav__item--current { 18 | A { 19 | font-weight: bold; 20 | text-decoration: none; 21 | color: inherit; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /_includes/links-list.html: -------------------------------------------------------------------------------- 1 | <{% if include.type %}{{ include.type }}{% else %}ul{% endif %} class="{{ include.parent }}__list">{% for link in include.list %} 2 |
  • 3 | {{ link.name }} 4 |
  • {% endfor %} 5 | 6 | -------------------------------------------------------------------------------- /_includes/page-nav.html: -------------------------------------------------------------------------------- 1 | <{% if include.type %}{{ include.type }}{% else %}ul{% endif %} class="{{ include.parent }}__list">{% for link in include.list %} 2 |
  • 3 | {{ link.name }} 4 | {% if link.source_name %} от {{ link.source_name }} {% endif %} 5 |
  • {% endfor %} 6 | 7 | -------------------------------------------------------------------------------- /_sass/_site-nav.scss: -------------------------------------------------------------------------------- 1 | .site-nav { 2 | @media (min-width: 600px) { 3 | display: flex; 4 | justify-content: space-between; 5 | align-items: center; 6 | } 7 | } 8 | 9 | .site-nav__list { 10 | display: flex; 11 | flex-wrap: wrap; 12 | 13 | margin: 0; 14 | padding: 0; 15 | list-style-type: none; 16 | line-height: 2; 17 | 18 | @media (max-width: 600px) { 19 | margin-bottom: 1.5rem; 20 | } 21 | } 22 | 23 | .site-nav__item { 24 | margin: 0 2rem 0 0 ; 25 | padding: 0; 26 | } 27 | 28 | .site-nav__item--current A { 29 | text-decoration: none; 30 | font-weight: bold; 31 | color: inherit; 32 | } -------------------------------------------------------------------------------- /_sass/button-example.scss: -------------------------------------------------------------------------------- 1 | .button-example { 2 | min-width: 130px; 3 | max-width: 140px; 4 | min-height: 45px; 5 | padding: 0; 6 | margin: 0 20px 10px 0; 7 | vertical-align: middle; 8 | background: yellowgreen; 9 | border: 3px solid rgba(0,0,0,.15); 10 | border-radius: 5px; 11 | font: inherit; 12 | font-size: 18px; 13 | font-weight: bold; 14 | line-height: 1.2; 15 | color: rgba(0,0,0,.55); 16 | 17 | &:hover { 18 | color: rgba(0,0,0,.75); 19 | } 20 | } 21 | 22 | .button-example--hp { 23 | padding: 10px 0; 24 | } 25 | 26 | .button-example--vp { 27 | padding: 0 10px; 28 | } 29 | 30 | .button-example--p { 31 | padding: 10px; 32 | } 33 | -------------------------------------------------------------------------------- /_layouts/post.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 |
    5 | 6 |
    7 |

    {{ page.title }}

    8 | 9 | 10 |
    11 | 12 |
    13 | {{ content }} 14 |
    15 | 16 |
    17 | -------------------------------------------------------------------------------- /_sass/_post.scss: -------------------------------------------------------------------------------- 1 | /* Title 2 | --------------------------------------------- */ 3 | 4 | .post__title { 5 | margin-top: 0; 6 | 7 | .page--index & { 8 | display: none; 9 | } 10 | } 11 | 12 | .post__anchor { 13 | 14 | &:link, 15 | &:visited { 16 | text-decoration: none; 17 | color: #DDD; 18 | } 19 | 20 | &:hover { 21 | color: $link-color; 22 | } 23 | } 24 | 25 | /* Conclusion 26 | --------------------------------------------- */ 27 | 28 | .post__conclusion { 29 | padding: 0 20px; 30 | border: 2px solid yellowgreen; 31 | border-left-width: 20px; 32 | border-radius: 5px; 33 | } 34 | 35 | .post__conclusion-title { 36 | margin-bottom: 0; 37 | } 38 | 39 | .post__conclusion-list, 40 | .post__conclusion-text { 41 | margin-top: 5px; 42 | } 43 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Частые ошибки начинающих и способы их избежать 4 | id: index 5 | --- 6 | 7 |
    8 |

    Здесь собраны частые ошибки начинающих и способы их избежать. Статьи состоят из разбора, примеров кода и подсказок как проверить код в браузере.

    9 | 10 |

    Почитайте:

    11 | 18 |
    19 | -------------------------------------------------------------------------------- /_includes/icon-twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /_includes/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ site.title }}{% if page.title %} • {{ page.title }}{% endif %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /_includes/icon-github.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /_sass/_socials.scss: -------------------------------------------------------------------------------- 1 | .socials { 2 | display: flex; 3 | list-style-type: none; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | .socials__item { 8 | margin-right: 1rem; 9 | margin-bottom: 0; 10 | } 11 | .socials__icon { 12 | transition: all .25s; 13 | } 14 | .socials__icon--github { 15 | color: #000; 16 | 17 | &:hover { 18 | color: lighten( #000, 50%); 19 | } 20 | } 21 | 22 | .socials__icon--twitter { 23 | color: #1da1f2; 24 | 25 | &:hover { 26 | color: lighten( #1da1f2, 20%); 27 | } 28 | } 29 | .socials__icon svg { 30 | display: block; 31 | width: 1.4em; 32 | height: 1.4em; 33 | 34 | @media (min-width: 800px) { 35 | width: 1.2em; 36 | height: 1.2em; 37 | } 38 | 39 | } 40 | .socials__icon path { 41 | fill: currentColor; 42 | } 43 | .socials__username { 44 | display: none; 45 | } -------------------------------------------------------------------------------- /_sass/_compare.scss: -------------------------------------------------------------------------------- 1 | .compare { 2 | margin: 0 -10px; 3 | padding: 0; 4 | list-style: none; 5 | 6 | display: flex; 7 | justify-content: space-between; 8 | flex-wrap: wrap; 9 | } 10 | 11 | .compare__item { 12 | flex-grow: 1; 13 | margin: 0 10px 20px; 14 | display: flex; 15 | flex-direction: column; 16 | } 17 | .compare__item--bad .compare__title, 18 | .compare__item--bad .highlight { 19 | background: hsl(20, 100%, 75%); 20 | } 21 | .compare__item--good .compare__title, 22 | .compare__item--good .highlight { 23 | background: hsl(90, 100%, 72%); 24 | } 25 | 26 | .compare__title { 27 | align-self: flex-start; 28 | margin: 0; 29 | padding: 5px 15px 0; 30 | border-radius: 10px 10px 0 0; 31 | font-weight: normal; 32 | text-transform: uppercase; 33 | font-size: 12px; 34 | line-height: 1; 35 | } 36 | 37 | .compare__item .highlight { 38 | padding: 0 5px; 39 | box-sizing: border-box; 40 | border-radius: 0 10px 10px 10px; 41 | } 42 | .compare__item .language-css, 43 | .compare__item .language-html { 44 | margin-top: 5px; 45 | margin-bottom: 5px; 46 | border: 0; 47 | font-size: 12px; 48 | } 49 | -------------------------------------------------------------------------------- /_sass/_layout.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | min-width: 300px; 3 | max-width: 1200px; 4 | margin: 0 auto; 5 | padding: 0 2rem 3rem; 6 | box-sizing: border-box; 7 | } 8 | 9 | .site-content { 10 | @include clear; 11 | } 12 | 13 | @media (max-width: 1000px) { 14 | .site-content { 15 | display: flex; 16 | flex-direction: column; 17 | } 18 | 19 | .main { 20 | order: 1; 21 | } 22 | 23 | .aside { 24 | margin-bottom: 2rem; 25 | } 26 | 27 | .page--index { 28 | .main { 29 | order: 0; 30 | margin-bottom: 2rem; 31 | } 32 | .aside { 33 | margin-bottom: 0; 34 | } 35 | } 36 | } 37 | 38 | @media (min-width: 1000px) { 39 | .main { 40 | float: left; 41 | width: 100%; 42 | padding-right: 320px; 43 | box-sizing: border-box; 44 | } 45 | 46 | .aside { 47 | float: right; 48 | margin-left: -300px; 49 | width: 280px; 50 | padding-top: 1rem; 51 | 52 | position: sticky; 53 | top: 20px; 54 | } 55 | } 56 | 57 | /* Site parts 58 | --------------------------------------------- */ 59 | 60 | .site-header { 61 | margin-bottom: 2em; 62 | padding-bottom: 1.5rem; 63 | border-bottom: 3px solid #EEE; 64 | 65 | @include clear; 66 | } 67 | 68 | .site-footer { 69 | margin-top: 3rem; 70 | padding-top: 1.5rem; 71 | border-top: 3px solid #EEE; 72 | 73 | @include clear; 74 | } 75 | -------------------------------------------------------------------------------- /feed.xml: -------------------------------------------------------------------------------- 1 | --- 2 | layout: null 3 | --- 4 | 5 | 6 | 7 | {{ site.title | xml_escape }} 8 | {{ site.description | xml_escape }} 9 | {{ site.url }}{{ site.baseurl }}/ 10 | 11 | {{ site.time | date_to_rfc822 }} 12 | {{ site.time | date_to_rfc822 }} 13 | Jekyll v{{ jekyll.version }} 14 | {% for post in site.posts limit:10 %} 15 | 16 | {{ post.title | xml_escape }} 17 | {{ post.content | xml_escape }} 18 | {{ post.date | date_to_rfc822 }} 19 | {{ post.url | prepend: site.baseurl | prepend: site.url }} 20 | {{ post.url | prepend: site.baseurl | prepend: site.url }} 21 | {% for tag in post.tags %} 22 | {{ tag | xml_escape }} 23 | {% endfor %} 24 | {% for cat in post.categories %} 25 | {{ cat | xml_escape }} 26 | {% endfor %} 27 | 28 | {% endfor %} 29 | 30 | 31 | -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% include head.html %} 5 | 6 | 7 | 8 |
    9 | {% include header.html %} 10 | 11 |
    12 |
    13 | {{ content }} 14 |
    15 | 16 | 39 |
    40 | 41 | {% include footer.html %} 42 | 43 |
    44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | # Welcome to Jekyll! 2 | # 3 | # This config file is meant for settings that affect your whole blog, values 4 | # which you are expected to set up once and rarely need to edit after that. 5 | # For technical reasons, this file is *NOT* reloaded automatically when you use 6 | # 'jekyll serve'. If you change this file, please restart the server process. 7 | 8 | # jekyll serve --livereload — run server 9 | 10 | # Site settings 11 | title: 'HTML & CSS: как не надо' 12 | description: > # this means to ignore newlines until "baseurl:" 13 | Ошибки при создании разметки и что с ними делать 14 | baseurl: '/bad-practices' # the subpath of your site, e.g. /blog 15 | 16 | # url: "http://yourdomain.com" # the base hostname & protocol for your site 17 | twitter_username: yoksel 18 | github_username: yoksel 19 | 20 | # Build settings 21 | markdown: kramdown 22 | highlighter: rouge 23 | 24 | # More › http://kramdown.gettalong.org/quickref.html 25 | # Options › http://kramdown.gettalong.org/options.html 26 | kramdown: 27 | input: GFM 28 | # https://github.com/jekyll/jekyll/pull/4090 29 | syntax_highlighter: rouge 30 | 31 | # Rouge Highlighter in Kramdown › http://kramdown.gettalong.org/syntax_highlighter/rouge.html 32 | # span, block element options fall back to global 33 | syntax_highlighter_opts: 34 | # Rouge Options › https://github.com/jneen/rouge#full-options 35 | css_class: 'highlight' 36 | #line_numbers: true # bad idea, spans don't need linenos and would inherit this option 37 | span: 38 | line_numbers: false 39 | block: 40 | line_numbers: true 41 | start_line: 1 42 | 43 | permalink: /title/ 44 | 45 | menu: 46 | - name: 'Введение' 47 | url: '/' 48 | - name: 'Стили текста' 49 | url: '/font-styles/' 50 | - name: 'Стили в медиавыражениях' 51 | url: '/styles-in-mediaquery/' 52 | - name: 'Блоки с текстом' 53 | url: '/blocks-with-text/' 54 | - name: 'Верстка форм' 55 | url: '/forms-markup/' 56 | - name: 'Контейнеры' 57 | url: '/containers/' 58 | 59 | projects: 60 | - name: 'Простые правила разметки' 61 | url: 'https://yoksel.github.io/easy-markup/' 62 | - name: 'Простой CSS' 63 | url: 'https://yoksel.github.io/easy-css/' 64 | - name: 'HTML & CSS: как не надо' 65 | url: '/' 66 | 67 | socials: 68 | - name: '@yoksel' 69 | url: 'https://twitter.com/yoksel' 70 | - name: 'Проект на GitHub' 71 | url: 'https://github.com/yoksel/bad-practices/' 72 | -------------------------------------------------------------------------------- /assets/css/prism.css: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript+sass */ 2 | /** 3 | * okaidia theme for JavaScript, CSS and HTML 4 | * Loosely based on Monokai textmate theme by http://www.monokai.nl/ 5 | * @author ocodia 6 | */ 7 | 8 | code[class*="language-"], 9 | pre[class*="language-"] { 10 | color: #f8f8f2; 11 | background: none; 12 | text-shadow: 0 1px rgba(0, 0, 0, 0.3); 13 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | word-wrap: normal; 19 | line-height: 1.5; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | } 30 | 31 | /* Code blocks */ 32 | pre[class*="language-"] { 33 | padding: 1em 1em 1em 1.5em; 34 | margin: .5em 0; 35 | overflow: auto; 36 | border-radius: 0.3em; 37 | } 38 | 39 | :not(pre) > code[class*="language-"], 40 | pre[class*="language-"] { 41 | background: #272822; 42 | } 43 | 44 | /* Inline code */ 45 | :not(pre) > code[class*="language-"] { 46 | padding: .1em; 47 | border-radius: .3em; 48 | white-space: normal; 49 | } 50 | 51 | .token.comment, 52 | .token.prolog, 53 | .token.doctype, 54 | .token.cdata { 55 | color: slategray; 56 | } 57 | 58 | .token.punctuation { 59 | color: #f8f8f2; 60 | } 61 | 62 | .namespace { 63 | opacity: .7; 64 | } 65 | 66 | .token.property, 67 | .token.tag, 68 | .token.constant, 69 | .token.symbol, 70 | .token.deleted { 71 | color: #f92672; 72 | } 73 | 74 | .token.boolean, 75 | .token.number { 76 | color: #ae81ff; 77 | } 78 | 79 | .token.selector, 80 | .token.attr-name, 81 | .token.string, 82 | .token.char, 83 | .token.builtin, 84 | .token.inserted { 85 | color: #a6e22e; 86 | } 87 | 88 | .token.operator, 89 | .token.entity, 90 | .token.url, 91 | .language-css .token.string, 92 | .style .token.string, 93 | .token.variable { 94 | color: #f8f8f2; 95 | } 96 | 97 | .token.atrule, 98 | .token.attr-value, 99 | .token.function { 100 | color: #e6db74; 101 | } 102 | 103 | .token.keyword { 104 | color: #66d9ef; 105 | } 106 | 107 | .token.regex, 108 | .token.important { 109 | color: #fd971f; 110 | } 111 | 112 | .token.important, 113 | .token.bold { 114 | font-weight: bold; 115 | } 116 | .token.italic { 117 | font-style: italic; 118 | } 119 | 120 | .token.entity { 121 | cursor: help; 122 | } 123 | 124 | -------------------------------------------------------------------------------- /_sass/_form-example.scss: -------------------------------------------------------------------------------- 1 | $color-input: #DDD; 2 | $color-input-hover: #999; 3 | $color-input-focus: lightseagreen; 4 | 5 | .form-example { 6 | border: 2px solid #DDD; 7 | padding: 20px; 8 | max-width: 350px; 9 | } 10 | 11 | .form-example__group { 12 | margin: 20px 0; 13 | padding: 0; 14 | border: none; 15 | } 16 | 17 | .form-example__title { 18 | float: left; 19 | width: 100%; 20 | margin-bottom: 10px; 21 | font-weight: bold; 22 | } 23 | 24 | .form-example__list { 25 | padding: 0; 26 | margin: 0; 27 | list-style-type: none; 28 | clear: both; 29 | display: flex; 30 | flex-wrap: wrap; 31 | } 32 | .form-example__item { 33 | margin-bottom: 10px; 34 | flex-basis: 50%; 35 | display: flex; 36 | } 37 | 38 | .form-example__label { 39 | display: block; 40 | position: relative; 41 | 42 | &--text { 43 | font-weight: bold; 44 | } 45 | 46 | &--radio { 47 | padding-right: 10px; 48 | } 49 | &--radio::before { 50 | content: ""; 51 | display: inline-block; 52 | vertical-align: middle; 53 | width: 30px; 54 | height: 30px; 55 | margin-right: 5px; 56 | border-radius: 50%; 57 | border: 2px solid $color-input; 58 | transition: all .15s; 59 | } 60 | &--radio:hover::before { 61 | border-color: $color-input-hover; 62 | } 63 | .form-example__input--radio:checked + &::before { 64 | background: radial-gradient(black 8px, transparent 9px); 65 | } 66 | 67 | .form-example__input--radio:active + &::before, 68 | .form-example__input--radio:focus + &::before { 69 | border-color: $color-input-focus; 70 | } 71 | } 72 | 73 | .form-example__input { 74 | font: inherit; 75 | font-weight: normal; 76 | 77 | &--text { 78 | margin-left: 10px; 79 | border: 2px solid $color-input; 80 | outline: none; 81 | } 82 | &--text:hover { 83 | border-color: $color-input-hover; 84 | } 85 | &--text:focus { 86 | border-color: $color-input-focus; 87 | } 88 | } 89 | 90 | .form-example--display-none { 91 | .form-example__input--radio { 92 | display: none; 93 | } 94 | } 95 | 96 | .form-example__button { 97 | margin-top: 20px; 98 | background: #333; 99 | padding: 5px 30px; 100 | border: none; 101 | border-radius: 5px; 102 | color: #FFF; 103 | font: inherit; 104 | cursor: pointer; 105 | 106 | &:hover { 107 | background: #555; 108 | } 109 | &:focus { 110 | background: $color-input-focus; 111 | outline: none; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /styles-in-mediaquery/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Не дублируйте стили в медиавыражениях 4 | 5 | links: 6 | - url: "#why" 7 | name: "Почему? " 8 | - url: "#how-to-see" 9 | name: "Как это увидеть?" 10 | - url: "#right-way" 11 | name: "А как надо?" 12 | --- 13 | 14 |
    15 |
    Почему? #
    16 |
    17 |

    Использование медиавыражений не сбрасывает предыдущие стили, поэтому не нужно их дублировать для каждого брикпойнта.

    18 |
    19 | 20 |
    Как это увидеть? #
    21 |
    22 |

    В браузере в инструментах разработчика:

    23 | 24 |
    25 | Дублирование стилей в медиавыражении в инструментах разработчика 26 | 27 |
    Все перечёркнутые свойства были унаследованы, но затем были перезаписаны, хотя некоторые значения остались теми же.
    28 |
    29 |
    30 | 31 |
    А как надо? #
    32 |
    33 |

    В медиавыражение добавляйте только те свойства, которые меняются:

    34 | 35 | 80 | 81 |

    Такой код быстрее писать и проще поддерживать.

    82 |
    83 |
    84 | 85 |
    86 |

    Итого

    87 | 88 |

    Медиавыражения не сбрасывают стили. Добавляйте в них только те свойства, которые нужно переопределить.

    89 |
    90 | -------------------------------------------------------------------------------- /_sass/_base.scss: -------------------------------------------------------------------------------- 1 | HTML { 2 | font-size: 12px; 3 | } 4 | 5 | BODY { 6 | margin: 0; 7 | padding: 0; 8 | background-color: $background-color; 9 | font: 16px/1.5 Trebuchet MS, sans-serif; 10 | color: $text-color; 11 | } 12 | 13 | /* Links 14 | --------------------------------------------- */ 15 | 16 | A { 17 | color: $link-color; 18 | text-decoration: underline; 19 | transition: all .25s; 20 | 21 | &:visited { 22 | color: darken($link-color, 15%); 23 | } 24 | 25 | &:hover { 26 | color: lighten($link-color, 15%); 27 | text-decoration: none; 28 | } 29 | } 30 | 31 | /* Headers 32 | --------------------------------------------- */ 33 | 34 | H1, H2, H3, H4, H5 { 35 | margin: .5em 0; 36 | } 37 | 38 | H1 { 39 | font-size: 2rem; 40 | line-height: 1.4; 41 | 42 | @media (min-width: 800px) { 43 | font-size: 3.5rem; 44 | line-height: 1.5; 45 | } 46 | 47 | A, 48 | A:visited { 49 | text-decoration: none; 50 | color: inherit; 51 | } 52 | 53 | A:hover { 54 | text-decoration: underline; 55 | } 56 | } 57 | 58 | H2 { 59 | font-size: 1.8rem; 60 | 61 | @media (min-width: 800px) { 62 | font-size: 2.5rem; 63 | } 64 | } 65 | 66 | H3 { 67 | font-size: 1.5rem; 68 | 69 | @media (min-width: 800px) { 70 | font-size: 2rem; 71 | } 72 | 73 | SUP { 74 | font-size: .7rem; 75 | opacity: .5; 76 | 77 | &:hover { 78 | opacity: 1; 79 | } 80 | } 81 | } 82 | 83 | H4 { 84 | font-size: 1.2rem; 85 | 86 | @media (min-width: 800px) { 87 | font-size: 1.5rem; 88 | } 89 | } 90 | 91 | H5 { 92 | font-size: 1.25rem; 93 | } 94 | 95 | /* Images 96 | --------------------------------------------- */ 97 | 98 | FIGURE { 99 | margin: 0; 100 | } 101 | 102 | PRE + FIGURE { 103 | margin-top: 30px; 104 | } 105 | 106 | .figure--has-figcaption { 107 | margin: 1em 0; 108 | display: flex; 109 | flex-wrap: wrap; 110 | align-items: flex-start; 111 | 112 | IMG { 113 | margin-right: 20px; 114 | margin-bottom: 10px; 115 | } 116 | 117 | @include clear; 118 | } 119 | 120 | FIGCAPTION { 121 | flex-basis: 300px; 122 | flex-grow: 1; 123 | font-size: 14px; 124 | font-style: italic; 125 | color: #555 126 | } 127 | 128 | IMG { 129 | border: 1px solid #DDD; 130 | max-width: 100%; 131 | height: auto; 132 | } 133 | 134 | /* Lists 135 | --------------------------------------------- */ 136 | 137 | OL, UL { 138 | padding-left: 0; 139 | 140 | @media (min-width: 600px) { 141 | padding-left: 20px; 142 | } 143 | @media (min-width: 800px) { 144 | padding-left: 30px; 145 | } 146 | } 147 | 148 | LI { 149 | margin-bottom: .5rem; 150 | } 151 | 152 | DL { 153 | margin-bottom: 2rem; 154 | } 155 | 156 | DT { 157 | font-weight: bold; 158 | } 159 | 160 | LI > DL > DT { 161 | font-size: 1.1em; 162 | } 163 | 164 | DD > DL { 165 | margin-top: .5rem ; 166 | } 167 | 168 | DD:not(:last-child) { 169 | margin-bottom: 2rem; 170 | } 171 | 172 | /* Counters 173 | --------------------------------------------- */ 174 | 175 | OL { 176 | counter-reset: list-counter; 177 | list-style: none; 178 | } 179 | 180 | OL OL { 181 | list-style-type: decimal; 182 | padding-left: 2.4em; 183 | 184 | @media (min-width: 600px) { 185 | padding-left: 3em; 186 | } 187 | } 188 | 189 | OL > LI { 190 | counter-increment: list-counter; 191 | } 192 | OL > LI > H3:before { 193 | content: counter(list-counter) '. '; 194 | } 195 | 196 | .list--has-numbers { 197 | list-style-type: decimal; 198 | } 199 | 200 | /* Codes 201 | --------------------------------------------- */ 202 | 203 | PRE, 204 | CODE { 205 | border: 1px solid #DDD; 206 | border-radius: 3px; 207 | background-color: #EEE; 208 | font-size: 15px; 209 | } 210 | 211 | CODE { 212 | padding: 1px 5px; 213 | // font-family: Courier New, monospace; 214 | } 215 | 216 | PRE { 217 | padding: 8px 12px; 218 | overflow-x: auto; 219 | 220 | > code { 221 | border: 0; 222 | padding-right: 0; 223 | padding-left: 0; 224 | } 225 | } 226 | 227 | .language-css, 228 | .language-html { 229 | font-size: 13px; 230 | } 231 | 232 | /* Tip 233 | --------------------------------------------- */ 234 | 235 | .tip { 236 | font-style: italic; 237 | } 238 | 239 | /* Relative Project Link 240 | --------------------------------------------- */ 241 | 242 | .related-project { 243 | display: block; 244 | max-width: 350px; 245 | margin-left: auto; 246 | margin-right: auto; 247 | padding: 1rem; 248 | border: 3px solid #EEE; 249 | text-align: center; 250 | } 251 | .related-project__text { 252 | position: relative; 253 | display: inline-block; 254 | margin-left: 1.5em; 255 | 256 | &::before { 257 | content: '\1F4D6'; 258 | position: absolute; 259 | left: -1.5em; 260 | line-height: 1.6; 261 | } 262 | } 263 | 264 | /* Visually hidden 265 | --------------------------------------------- */ 266 | 267 | .visuallyhidden { 268 | position: absolute; 269 | 270 | width: 1px; 271 | height: 1px; 272 | margin: -1px; 273 | border: 0; 274 | padding: 0; 275 | 276 | white-space: nowrap; 277 | 278 | clip-path: inset(100%); 279 | clip: rect(0 0 0 0); 280 | overflow: hidden; 281 | } 282 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /font-styles/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Не дублируйте стили текста 4 | 5 | links: 6 | - url: "#why" 7 | name: "Почему?" 8 | - url: "#how-to-see" 9 | name: "Как это увидеть?" 10 | - url: "#right-way" 11 | name: "А как надо?" 12 | - url: "#gotchas" 13 | name: "Подводные камни" 14 | --- 15 | 16 |

    Не нужно задавать разным элементам одинаковые стили текста.

    17 | 18 |
    19 |
    Почему? #
    20 |
    21 |

    Это не имеет смысла и засоряет код. Воспользуйтесь наследованием: задайте стили для текста родительскому элементу, и дочерние элементы сами их унаследуют, вам не нужно для этого ничего делать.

    22 |
    23 | 24 |
    Как это увидеть? #
    25 |
    26 |

    В браузере в инструментах разработчика:

    27 | 28 |
    29 | Дублирование стилей видно в инструментах разработчика 30 | 31 |
    Все перечёркнутые свойства были унаследованы, но затем перезаписаны точно такими же. Так делать не надо.
    32 |
    33 |
    34 | 35 |
    А как надо? #
    36 |
    37 |

    Стили текста достаточно задать один раз в body, и они автоматом применятся ко всем элементам страницы.

    38 | 39 | 92 | 93 |

    Исправленный код значительно короче, с ним удобнее иметь дело. Также не тратится время на написание ненужных свойств.

    94 | 95 |

    Как проверить, что всё работает как надо? Там же, в инструментах разработчика:

    96 | 97 |
    98 | Стили не дублируются 99 |
    На скриншоте видно, что стили текста унаследовались из body, а цвет фона — нет (он показан бледным)
    100 |
    101 | 102 |

    Ещё один способ:

    103 | 104 |
    105 | Rendered Fonts в инструментах разработчика 106 | 107 |
    Во вкладке Computed поищите конкретные свойства или посмотрите в Rendered Fonts — там показывается какой шрифт в итоге применился к тексту.
    108 |
    109 | 110 |
    111 | Проверяем кастомный шрифт 112 | 113 |
    Там же можно проверить применился ли ваш красивый кастомный шрифт. Не смотря на то, что кастомный MyFancyFont объявлен в списке первым, текст в итоге отрисовался запасным — Georgia. Значит надо проверить правильно ли подключен кастомный шрифт.
    114 |
    115 | 116 |

    Если для некоторых элементов стили текста отличаются, достаточно переопределить отдельные свойства, а не писать всё заново. Также снижается вероятность ошибки, если потребуется поменять шрифт на всём сайте: это можно будет сделать в одном месте.

    117 |
    118 | 119 |
    Подводные камни #
    120 |
    121 |

    Стили текста для инпутов и кнопок задаются браузером:

    122 | 123 |
    124 | Стили для инпутов в инструментах разработчика 125 | 126 |
    Стили в body перечеркнуты, потому что перезаписаны стилями, который задаёт браузер — они отображаются на сером фоне, а в качестве источника указано user agent stylesheet.
    127 |
    128 | 129 |
    130 | В Rendered Fonts системный шрифт вместо кастомного 131 | 132 |
    В Rendered Fonts что-то совсем не то, что нужно.
    133 |
    134 | 135 | 136 |

    Это легко поправить, задав наследование явно:

    137 | 138 | {% highlight css %}input, textarea, select, button { 139 | font: inherit; 140 | }{% endhighlight %} 141 | 142 |

    Если нужно наследовать только семейство шрифтов, вместо font: inherit; задайте font-family: inherit;

    143 | 144 |
    145 | Проверяем наследование стилей для инпутов в инструментах разработчика 146 | 147 |
    Проверяем в Computed: Georgia.
    148 |
    149 | 150 |
    151 | Проверяем в инструментах разработчика какие стили и чем перезаписались 152 |
    Там же можно увидеть что чем перезаписалось.
    153 |
    154 |
    155 |
    156 | 157 |
    158 |

    Итого

    159 | 160 | 164 |
    165 | -------------------------------------------------------------------------------- /blocks-with-text/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Фиксированная высота и absolute для текстовых блоков 4 | 5 | links: 6 | - name: "Не задавайте текстовым блокам фиксированную высоту" 7 | url: "#fixed-height" 8 | - name: "Не используйте absolute для позиционирования текстовых блоков" 9 | url: "#absolute" 10 | 11 | additional_links: 12 | - name: "Краш-тест вёрстки" 13 | url: https://isqua.ru/blog/2016/06/19/crash-test-viorstki/ 14 | source_name: "Софьи Ильиновой" 15 | source_url: https://isqua.ru/blog 16 | 17 | --- 18 | 19 |
      20 |
    1. 21 |

      Не задавайте текстовым блокам фиксированную высоту #

      22 | 23 |
      24 |
      Почему? #
      25 |
      26 |

      При увеличении количества текста он вывалится наружу и может наложиться на нижележащие элементы.

      27 |
      28 | 29 |
      Как это увидеть? #
      30 |
      31 |

      Можно отредактировать исходный HTML, а можно выполнить в консоли браузера такую команду:

      32 | 33 | {% highlight js %}document.body.contentEditable = true{% endhighlight %} 34 | 35 |

      После этого вы сможете отредактировать любой текст на странице.

      36 | 37 |

      Добавим текст:

      38 | 39 |
      40 | При фиксированной ширине текст выпадает из родительского блока 41 | 42 |
      При фиксированной высоте текст не может растянуть блок и вываливается.
      43 |
      44 |
      45 | 46 |
      А как надо? #
      47 |
      48 | 49 |

      Решение зависит от того, что нужно сделать. Если у блока по макету есть минимальная высота, но контента недостаточно, чтобы его растянуть, используйте min-height:

      50 | 51 |
        52 |
      • 53 |
        Плохо
        54 | {% highlight css %}.container { 55 | height: 150px; 56 | padding: 10px; 57 | }{% endhighlight %} 58 |
      • 59 |
      • 60 |
        Хорошо
        61 | {% highlight css %}.container { 62 | min-height: 150px; 63 | padding: 10px; 64 | }{% endhighlight %} 65 |
      • 66 |
      67 | 68 | С min-height текст не выпадает из родителя 69 | 70 |

      min-height позволит добиться соответствия макету, а при добавлении контента блок просто вырастет вниз.

      71 | 72 |

      Ещё это может быть полезно как временное решение, чтобы разложить блоки по макету, до того, как будет стилизовано содержимое, после этого min-height убирается.

      73 | 74 |

      Если же контента достаточно, min-height не нужен, достаточно задать стили для текста и padding для блока, и его размеры будут определяться содержимым.

      75 | 76 |

      Если у блока есть явные границы, padding нужно задавать со всех сторон, чтобы при добавлении текста он не упёрся в края:

      77 | 78 |
      79 | 80 | 81 |
      Подопытная кнопка с минимальной шириной и высотой
      82 |
      83 | 84 |

      Изменим текст:

      85 | 86 |
      87 | 88 | 89 |
      Не хватает паддингов слева и справа
      90 |
      91 | 92 |
      93 | 94 | 95 |
      Не хватает вертикальных паддингов
      96 |
      97 | 98 |
      99 | 100 | 101 |
      Все паддинги на месте, добавление текста ничего не ломает
      102 |
      103 | 104 | 105 |

      106 |
      107 |
      108 | 109 |
    2. 110 |
    3. 111 |

      Не используйте absolute для позиционирования текстовых блоков #

      112 | 113 |
      114 |
      Почему? #
      115 |
      116 |

      При абсолютном позиционировании блок не влияет на размеры родителя. При увеличении количества текста он не сможет растянуть родительский блок и вывалится наружу.

      117 |
      118 | 119 |
      Как это увидеть? #
      120 |
      121 |

      Как и в предыдущем случае, можно отредактировать исходный HTML или использовать команду в консоли:

      122 | 123 | {% highlight js %}document.body.contentEditable = true{% endhighlight %} 124 | 125 |

      После чего добавить текст в интересующий элемент.

      126 | 127 |
      128 | Абсолютно спозиционированный элемент не растягивает родителя 129 | 130 |
      Абсолютно спозиционированный элемент не может растянуть родительский блок.
      131 |
      132 |
      133 | 134 |
      А как надо? #
      135 |
      136 |

      В этом случае лучше перенести position:absolute на картинку и добавить min-height, чтобы блок не схлопывался по высоте текста (иначе может вывалиться картинка):

      137 | 138 |
        139 |
      • 140 |
        Плохо
        141 | {% highlight css %}.block { 142 | position: relative; 143 | display: flex; 144 | width: 600px; 145 | padding: 20px; 146 | } 147 | .block__content { 148 | position: absolute; 149 | } 150 | .block__img { 151 | margin-left: auto; 152 | }{% endhighlight %} 153 |
      • 154 |
      • 155 |
        Хорошо
        156 | {% highlight css %}.block { 157 | position: relative; 158 | width: 600px; 159 | min-height: 350px; 160 | padding: 20px; 161 | } 162 | .block__content { 163 | z-index: 1; 164 | } 165 | .block__img { 166 | position: absolute; 167 | top: 20px; 168 | right: 20px; 169 | }{% endhighlight %} 170 |
      • 171 |
      172 | 173 |
      174 | min-height не даёт блоку схлопнуться, если текста мало 175 | 176 |
      Проверяем с коротким текстом: min-height не даёт блоку схлопнуться.
      177 |
      178 | 179 |
      180 | Длинный текст растягивает блок 181 | 182 |
      Проверяем с длинным текстом: текст растягивает блок, и ничего не выпадает.
      183 |
      184 | 185 |
      186 |
      187 |
    4. 188 |
    189 | 190 | 191 |
    192 |

    Итого

    193 | 194 | 198 |
    199 | -------------------------------------------------------------------------------- /assets/js/prism.js: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript+sass */ 2 | var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(\w+)\b/i,t=0,n=_self.Prism={util:{encode:function(e){return e instanceof a?new a(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(m instanceof a)){u.lastIndex=0;var y=u.exec(m),v=1;if(!y&&h&&p!=r.length-1){var b=r[p+1].matchedStr||r[p+1],k=m+b;if(p=m.length)continue;var _=y.index+y[0].length,P=m.length+b.length;if(v=3,P>=_){if(r[p+1].greedy)continue;v=2,k=k.slice(0,P)}m=k}if(y){g&&(f=y[1].length);var w=y.index+f,y=y[0].slice(f),_=w+y.length,S=m.slice(0,w),A=m.slice(_),O=[p,v];S&&O.push(S);var j=new a(i,c?n.tokenize(y,c):y,d,y,h);O.push(j),A&&O.push(A),Array.prototype.splice.apply(r,O)}}}}}return r},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r,l=0;r=a[l++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.matchedStr=a||null,this.greedy=!!r};if(a.stringify=function(e,t,r){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return a.stringify(n,t,e)}).join("");var l={type:e.type,content:a.stringify(e.content,t,r),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:r};if("comment"==l.type&&(l.attributes.spellcheck="true"),e.alias){var i="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(l.classes,i)}n.hooks.run("wrap",l);var o="";for(var s in l.attributes)o+=(o?" ":"")+s+'="'+(l.attributes[s]||"")+'"';return"<"+l.tag+' class="'+l.classes.join(" ")+'" '+o+">"+l.content+""},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var t=JSON.parse(e.data),a=t.language,r=t.code,l=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),l&&_self.close()},!1),_self.Prism):_self.Prism;var r=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return r&&(n.filename=r.src,document.addEventListener&&!r.hasAttribute("data-manual")&&("loading"!==document.readyState?requestAnimationFrame(n.highlightAll,0):document.addEventListener("DOMContentLoaded",n.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); 3 | Prism.languages.markup={comment://,prolog:/<\?[\w\W]+?\?>/,doctype://,cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=.$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Prism.languages.xml=Prism.languages.markup,Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup; 4 | Prism.languages.css={comment:/\/\*[\w\W]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:/("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/,property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.util.clone(Prism.languages.css),Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/()[\w\W]*?(?=<\/style>)/i,lookbehind:!0,inside:Prism.languages.css,alias:"language-css"}}),Prism.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|').*?\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:Prism.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:Prism.languages.css}},alias:"language-css"}},Prism.languages.markup.tag)); 5 | Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:{pattern:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/}; 6 | Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,"function":/[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^\/])\/(?!\/)(\[.+?]|\\.|[^\/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0,greedy:!0}}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\\\|\\?[^\\])*?`/,greedy:!0,inside:{interpolation:{pattern:/\$\{[^}]+\}/,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/()[\w\W]*?(?=<\/script>)/i,lookbehind:!0,inside:Prism.languages.javascript,alias:"language-javascript"}}),Prism.languages.js=Prism.languages.javascript; 7 | !function(e){e.languages.sass=e.languages.extend("css",{comment:{pattern:/^([ \t]*)\/[\/*].*(?:(?:\r?\n|\r)\1[ \t]+.+)*/m,lookbehind:!0}}),e.languages.insertBefore("sass","atrule",{"atrule-line":{pattern:/^(?:[ \t]*)[@+=].+/m,inside:{atrule:/(?:@[\w-]+|[+=])/m}}}),delete e.languages.sass.atrule;var a=/((\$[-_\w]+)|(#\{\$[-_\w]+\}))/i,t=[/[+*\/%]|[=!]=|<=?|>=?|\b(?:and|or|not)\b/,{pattern:/(\s+)-(?=\s)/,lookbehind:!0}];e.languages.insertBefore("sass","property",{"variable-line":{pattern:/^[ \t]*\$.+/m,inside:{punctuation:/:/,variable:a,operator:t}},"property-line":{pattern:/^[ \t]*(?:[^:\s]+ *:.*|:[^:\s]+.*)/m,inside:{property:[/[^:\s]+(?=\s*:)/,{pattern:/(:)[^:\s]+/,lookbehind:!0}],punctuation:/:/,variable:a,operator:t,important:e.languages.sass.important}}}),delete e.languages.sass.property,delete e.languages.sass.important,delete e.languages.sass.selector,e.languages.insertBefore("sass","punctuation",{selector:{pattern:/([ \t]*)\S(?:,?[^,\r\n]+)*(?:,(?:\r?\n|\r)\1[ \t]+\S(?:,?[^,\r\n]+)*)*/,lookbehind:!0}})}(Prism); 8 | -------------------------------------------------------------------------------- /containers/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Не задавайте контейнерам ширину, равную брикпойнтам 4 | 5 | links: 6 | - url: "#why" 7 | name: "Почему? " 8 | - url: "#how-to-see" 9 | name: "Как это увидеть?" 10 | - url: "#right-way" 11 | name: "А как надо?" 12 | 13 | additional_links: 14 | - name: "Контент по центру, фон по ширине" 15 | url: https://isqua.ru/blog/2016/06/23/content-po-sentru-fon-po-shirinie/ 16 | source_name: "Софьи Ильиновой" 17 | source_url: https://isqua.ru/blog 18 | - name: "Full Width Containers in Limited Width Parents" 19 | url: https://css-tricks.com/full-width-containers-limited-width-parents/ 20 | source_name: "Css Tricks" 21 | source_url: https://css-tricks.com/ 22 | --- 23 | 24 |
    25 |
    Почему? #
    26 |
    27 |

    Это решение подходит для мобильных устройств, где прокрутки нет, но не годится для десктопных браузеров, где она есть и занимает место на странице.

    28 |
    29 | 30 |
    Как это увидеть? #
    31 |
    32 |

    Допустим, есть макет для страницы шириной 600px, контент должен занимать всю ширину. Зададим соответствующий код:

    33 | 34 | {% highlight css %}@media (min-width: 600px) { 35 | .container { 36 | width: 600px; 37 | } 38 | }{% endhighlight %} 39 | 40 |

    И посмотрим в браузере что получилось. Начнём с мобильных:

    41 | 42 | Контейнер с фиксированной шириной на мобильных 43 | 44 |

    На мобильных всё как надо. Десктоп:

    45 | 46 | Контейнер с фиксированной шириной на десктопе 47 | 48 |

    На десктопе появилась не только вертикальная прокрутка, но и горизонтальная. Почему?

    49 | 50 | body содержащий контейнер с фиксированной шириной на десктопе 51 | 52 |

    Потому что вертикальная прокрутка отъела часть ширины окна, и для контента осталось меньше места.

    53 | 54 |

    На десктопе body будет меньше ширины окна, поэтому на ширине окна 600px элемент той же ширины не поместится, что и вызовет появление горизонтальной прокрутки.

    55 | 56 |
    57 | 58 |
    А как надо? #
    59 |
    60 | 61 |

    Есть несколько вариантов решений, в зависимости от того, что требуется сделать, и свободного времени на это.

    62 | 63 |
    64 |
    Вариант с фиксированной колонкой
    65 |
    66 |

    На макете, как правило, предусмотрены поля. Можно измерить ширину контента без учёта этих полей и использовать это значение, задав колонке выравнивание по центру.

    67 | 68 |
      69 |
    • 70 |
      Плохо
      71 | {% highlight css %}@media (min-width: 600px) { 72 | .container { 73 | width: 600px; 74 | } 75 | }{% endhighlight %} 76 |
    • 77 |
    • 78 |
      Хорошо
      79 | {% highlight css %}@media (min-width: 600px) { 80 | .container { 81 | width: 500px; 82 | margin: 0 auto; 83 | } 84 | }{% endhighlight %} 85 |
    • 86 |
    87 | 88 |

    Так контейнер, который мог бы растопырить страницу, оказывается уже, чем доступное пространство, и горизонтальной прокрутки не будет.

    89 | 90 |

    Простое и быстрое решение.

    91 | 92 |

    Если всё же нужно ограничить ширину колонки вместе с полями внутри, вместо width используйте max-width:

    93 | 94 |
      95 |
    • 96 |
      Плохо
      97 | {% highlight css %}@media (min-width: 600px) { 98 | .container { 99 | width: 600px; 100 | } 101 | }{% endhighlight %} 102 |
    • 103 |
    • 104 |
      Хорошо
      105 | {% highlight css %}@media (min-width: 600px) { 106 | .container { 107 | max-width: 600px; 108 | } 109 | }{% endhighlight %} 110 |
    • 111 |
    112 | 113 |

    Так колонка не растянется шире заданного значения, а если места окажется недостаточно — сожмётся.

    114 |
    115 | 116 |
    Вариант с резиновой колонкой
    117 |
    118 |

    Чтобы на узких экранах конент не болтался узкой полосой, а занимал всю доступную ширину, можно сделать иначе: никак не ограничивать ширину контейнера (и он растянется на всю ширину body), но задать боковые margin или padding.

    119 | 120 |
      121 |
    • 122 |
      Плохо
      123 | {% highlight css %}@media (min-width: 600px) { 124 | .container { 125 | width: 600px; 126 | } 127 | }{% endhighlight %} 128 |
    • 129 |
    • 130 |
      Хорошо
      131 | {% highlight css %}@media (min-width: 600px) { 132 | .container { 133 | padding: 50px; 134 | } 135 | }{% endhighlight %} 136 |
    • 137 |
    138 | 139 |

    Контейнер без фиксированной ширины сам подгонится под доступное пространство не вызывая горизонтальную прокрутку, и будет тянуться вместе с окном браузера.

    140 | 141 |

    Не забудьте ограничить ширину колонки на десктопе, чтобы страницу не ратстягивало на всё окно, если у пользователя широкий экран.

    142 | 143 |

    Минус способа в том, что в этом случае также надо будет предусмотреть резиновое поведение содержимого, чтобы оно не сбивалось в кучу или не размазывалось по странице. Если на это нет времени, просто используйте способ с фиксированной колонкой.

    144 |
    145 | 146 |
    Вариант с фоном на всю ширину страницы
    147 |
    148 | 149 |

    Например, в макете требуется что-то такое:

    150 | 151 | Пример с фоном ширину страницы 152 | 153 |

    У шапки фон по ширине страницы, содержимое по центру.

    154 | 155 |

    На самом деле, вариант почти ничем не отличается от предыдущих. В случае с резиновой колонкой и паддингами, можно задать фон самой колонке, потому что она и так растянута на ширину страницы. В остальных случаях снаружи контейнера нужно просто задать обёртку, которой и будет задан фон.

    156 | 157 |

    Обёртка с фоном не будет ограничиваться по ширине и всегда будет растягиваться по ширине body не вызывая появление прокрутки. При этом содержимое внутри неё будет придерживаться заданной ширины в зависимости от выбранного способа.

    158 |
    159 | 160 | 161 |
    Вариант с картинкой на всю ширину страницы
    162 |
    163 | 164 |

    Иногда нужно, чтобы тянулась не вся колонка, а часть содержимого, например, картинка в статье:

    165 | 166 | Пример с картинкой на ширину страницы 167 | 168 |

    В этом случае ширина колонки с текстом делается с помощью какого-то из предыдущих способов, но для картинки потребуется дополнительный код.

    169 | 170 |

    Если нужно, чтобы картинка растягивалась и сжималась вместе с контентом, достаточно будет кода для картинки:

    171 | 172 | {% highlight css %}.img--panorama { 173 | position: relative; 174 | /* растягиваем по ширине окна */ 175 | width: 100vw; 176 | /* центрируем */ 177 | left: 50%; 178 | transform: translate(-50%); 179 | }{% endhighlight %} 180 | 181 | 182 |

    Но если требуется, чтобы размеры картинки не менялись, а всё, что не поместилось по ширине, просто обрезалось, тогда картинке нужна обёртка:

    183 | 184 | {% highlight html %}
    185 | ... 186 |
    {% endhighlight %} 187 | 188 |

    Теперь центрируется обёртка, и она же отвечает за выравнивание картинки по центру:

    189 | 190 | {% highlight css %}.panorama { 191 | position: relative; 192 | /* растягиваем по ширине окна */ 193 | width: 100vw; 194 | /* скрываем всё, что не поместилось в экран */ 195 | overflow: hidden; 196 | /* центрируем обёртку */ 197 | left: 50%; 198 | transform: translate(-50%); 199 | /* центрируем картинки */ 200 | display: flex; 201 | justify-content: center; 202 | } 203 | .panorama__img { 204 | /* фиксируем ширину картинки */ 205 | width: 982px; 206 | /* отключаем max-width, 207 | если он было задано выше */ 208 | max-width: none; 209 | }{% endhighlight %} 210 | 211 |

    На мобильных всё будет работать как задумано, но если открыть на десктопе, обнаружится горизонтальная прокрутка:

    212 | 213 | Пример с картинкой на ширину страницы с горизонтальной прокруткой на десктопе 214 | 215 |

    Почему?

    216 | 217 |

    Потому что 100vw — это не ширина body, а вся ширина окна вместе с прокруткой, то есть элемент такой ширины на странице не поместится, что и вызовет появление горизонтальной прокрутки.

    218 | 219 |

    Чтобы решить эту проблему, ближайшему родителю широкой картинки, растянутому на всю ширину страницы, задайте overflow: hidden.

    220 | 221 | Пример с картинкой на ширину страницы без горизонтальной прокрутки 222 | 223 |

    Горизонтальная прокрутка пропала, всё работает как надо.

    224 |
    225 |
    226 | 227 |
    228 |
    229 | 230 |
    231 |

    Итого

    232 | 233 |

    Не задавайте контейнерам ширину, равную брикпойнтам, это может вызвать появление горизонтальной прокрутки в десктопных браузерах. Задавайте меньшую ширину или используйте паддинги/марджины для контейнеров с нефиксированной шириной.

    234 |
    235 | -------------------------------------------------------------------------------- /forms-markup/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Верстка форм 4 | 5 | links: 6 | - url: "#div-h3" 7 | name: "Не используйте для разметки групп инпутов div и h1-h6" 8 | - url: "#legend-label" 9 | name: "Не используйте legend вместо label" 10 | - url: "#hidden-inputs" 11 | name: "Не используйте display: none для скрытия инпутов" 12 | --- 13 | 14 |
      15 |
    1. 16 |

      Не используйте для разметки групп инпутов div и h1-h6 #

      17 | 18 |
      19 |
      Почему? #
      20 |
      21 |

      Для разметки групп полей есть более подходящие теги: fieldset и legend. Они не только внесут разнообразие в код, но также сделают вашу форму более доступной. 22 |

      23 | 24 |
      А как надо? #
      25 |
      26 | 27 |

      С чистыми fieldset и legend могут возникать затруднения при стилизации и позиционировании контента, но это легко решается дополнительными обёртками.

      28 | 29 |

      Например, так как fieldset используется для групп полей, а у каждого поля есть лейбл, каждой паре input + label обычно требуется обёртка, и здесь можно удобно использовать ненумерованные списки (ul). После этого можно всё позиционирование делать для списка и его элементов, и с раскладкой больше не будет никаких проблем. Чтобы вместе с инпутом не читалась информация об элементах списка, его нужно скрыть от скринридеров, задав role="none".

      30 | 31 |

      legend ведёт себя своеобразно, но его можно вырвать со своего места с помощью float: left, а для позиционирования текста внутри legend завернуть текст в спаны.

      32 | 33 |
        34 |
      • 35 |
        Плохо
        36 | {% highlight html %}
        37 |

        Контакты

        38 | 39 | 40 | 43 | 44 | 45 | 48 |
        {% endhighlight %} 49 |
      • 50 |
      • 51 |
        Хорошо
        52 | {% highlight html %}
        53 | Контакты 54 | 55 |
          56 |
        • 57 | 58 | 61 |
        • 62 |
        • 63 | 64 | 67 |
        • 68 |
        69 |
        {% endhighlight %} 70 |
      • 71 |
      72 | 73 | 74 |
      75 |
      76 | 77 |
    2. 78 |
    3. 79 |

      Не используйте legend вместо label #

      80 | 81 |
      82 |
      Почему? #
      83 |
      84 |

      В некоторых макетах можно увидеть, что название для textarea выглядит как legend, и возникает соблазн поместить textarea в fieldset, а название поля поместить в legend.

      85 | 86 |

      Это будет не самым правильными решением, потому что у всех инпутов должны быть лейблы. Если добавить скрытый лейбл, его содержимое будет дублировать уже имеющийся legend, и всё это вместе будет выглядеть довольно странно.

      87 |
      88 | 89 |
      Как это увидеть? #
      90 |
      91 |

      Посмотрите на форму без стилей:

      92 | 93 |
      94 |
      95 | 96 | Опишите привычки кота 97 | 98 | 103 |
      104 | 108 |
      109 |
      110 |
      111 | 112 |
      А как надо? #
      113 |
      114 |

      Для одиночного текстового поля не нужны fieldset и legend, они для групп полей. Если такому полю требуется обёртка, можно использовать div. Название поля нужно поместить в label.

      115 | 116 |

      Если стилизация legend не привязана к тегу, вы без проблем можете использовать эти же стили (а лучше класс) для стилизации лейбла.

      117 | 118 |
        119 |
      • 120 |
        Плохо
        121 | {% highlight html %}
        122 | 123 | Опишите привычки кота 124 | 125 | 130 | 131 | 135 |
        {% endhighlight %} 136 |
      • 137 |
      • 138 |
        Хорошо
        139 | {% highlight html %}
        140 | 144 | 145 | 149 |
        {% endhighlight %} 150 |
      • 151 |
      152 | 153 |

      Теперь ничего не дублируется:

      154 | 155 |
      156 |
      157 | 161 |
      162 | 166 |
      167 |
      168 | 169 |
      170 |
      171 |
    4. 172 | 173 |
    5. 174 |

      Не используйте display: none для скрытия инпутов #

      175 | 176 |
      177 |
      Почему? #
      178 |
      179 |

      Инпуты, спрятанные таким образом, становятся полностью недоступны для скринридеров и навигации с клавиатуры

      180 |
      181 | 182 |
      Как это увидеть? #
      183 |
      184 |

      Установите фокус в первое поле и перемещаясь по форме с помощью Tab и стрелок попробуйте выбрать цвет кота:

      185 | 186 |
      188 | 196 | 197 |
      198 | Цвет кота 199 | 200 |
        201 |
      • 202 | 208 | 213 |
      • 214 |
      • 215 | 220 | 225 |
      • 226 |
      • 227 | 233 | 238 |
      • 239 |
      • 240 | 249 | 254 |
      • 255 |
      256 |
      257 | 258 | 266 | 267 | 268 |
      269 | 270 |

      Ничего не получится, с клавиатуры выбор цвета недоступен.

      271 | 272 |
      273 | 274 |
      А как надо? #
      275 |
      276 |

      Для скрытия инпутов используйте класс .visuallyhidden:

      277 | 278 | {% highlight css %}.visuallyhidden { 279 | position: absolute; 280 | width: 1px; 281 | height: 1px; 282 | margin: -1px; 283 | border: 0; 284 | padding: 0; 285 | white-space: nowrap; 286 | clip-path: inset(100%); 287 | clip: rect(0 0 0 0); 288 | overflow: hidden; 289 | }{% endhighlight %} 290 | 291 |

      Это скроет инпут для обычных пользователей, но оставит его доступным для скринридеров. Почитать подробнее можно тут.

      292 | 293 |

      Попробуйте теперь с помощью Tab и стрелок выбрать цвет кота (чтобы выбрать цвет нажмите пробел):

      294 | 295 |
      297 | 305 | 306 |
      307 | Цвет кота 308 | 309 |
        310 |
      • 311 | 320 | 325 |
      • 326 |
      • 327 | 335 | 340 |
      • 341 |
      • 342 | 351 | 356 |
      • 357 |
      • 358 | 367 | 372 |
      • 373 |
      374 |
      375 | 376 | 384 | 385 | 386 |
      387 | 388 |

      Всё работает.

      389 | 390 |
      391 |
      392 |
    6. 393 |
    394 | 395 | 396 |
    397 |

    Итого

    398 | 399 |
      400 |
    • Не используйте для разметки групп инпутов div и h1-h6, есть более подходящие теги: fieldset и legend.
    • 401 |
    • Не используйте legend вместо label для одиночного поля, ему не нужны fieldset и legend, достаточно div и label.
    • 402 |
    • Не используйте display: none для скрытия инпутов, они становятся недоступны для скринридеров и навигации с клавиатуры. Скрывайте с помощью .visuallyhidden
    • 403 |
    404 |
    405 | --------------------------------------------------------------------------------