preface

I recently stumbled on a mobile Web project and felt the need to document them and share them with new entrants to mobile Web development.

First, start with the layout

Generally speaking, the overall layout of the mobile terminal can be divided into three parts, namely header, main and footer. Header and footer are fixed in height and are fixed at the top and bottom of the page respectively, while Main occupies the rest of the page and can be scrolled.









The page layout is as follows:


<body>

  <div class="header"></div>

  <div class="main"></div>

  <div class="footer"></div>

</body>
Copy the code

According to the scrolling position of the page, there are two layouts, one is to scroll the body, the other is to fix the height of the body at 100%, and scroll in the main.


The first layout has the advantage that the address bar of the page is hidden as the body scrolls, and scrolling the body is smoother on Android devices. Consider this if you have a similar requirement in your project.

The layout is implemented as follows:

body {
  overflow: auto;
}

.header,
.footer {
  position: fixed;
  left: 0;
  right: 0;
  height: 44px;
}

.header {
  top: 0;
}

.footer {
  bottom: 0;
}

.main {
  height: 100%;
  padding: 44px 0;
}
Copy the code

The first case is best for long list pages, where the entire page needs to scroll except for the header and footer, but many times when you only want one element of the page to scroll, the second case is used.


This page layout can be implemented in three relatively simple ways:

  1. Fixed position
  2. Absolute positioning
  3. Flex positioning





The easiest way to think of is fixed positioning, the implementation is as follows:

html, body {
  height: 100%;
  overflow: hidden;
}
.header,
.footer {
  position: fixed;
  left: 0;
  right: 0;
  height: 44px;
}


.header {
  top: 0;
}


.footer {
  bottom: 0;
}


.main {
  height: 100%;
  padding: 44px 0;
  box-sizing: border-box;
}
Copy the code

Fixed positioning is simple to implement and can be displayed normally in most browsers. However, fixed positioning will have compatibility problems on mobile terminals, which will be mentioned later. Therefore, it is not recommended to implement this method.


Absolute positioning is similar to fixed positioning. You only need to change the position of the header footer to absolute.


If you are careful, you may have noticed that there is no overflow in main, because there is a pit in main. It is the same for absolute positioning and fixed positioning. For the convenience of description, we only say fixed positioning (also in absolute positioning). On the PC, there is no problem, but on the mobile, if main sets overflow to true, the header will be covered by Main. If the sibling immediately following the fixed location node is scrollable (that is, with overflow set to true), the fixed node will be covered by the sibling following it.


There are many ways to solve this problem, since it is fixed after the scrollable sibling node will have this pit, as long as his condition has an untenable, there are the following solutions:


  1. Make fixed position node not followed by scrollable node
  2. I don’t want scroll nodes to cover fixed nodes





The first method has the following options:

1. Place all fixed nodes after scroll elements, i.e., header nodes after main nodes

<body>
  <div class="main"></div>
  <div class="header"></div>
  <div class="footer"></div>
</body>
Copy the code

But this is obviously not in line with ordinary people’s habits of thinking, code readability is reduced.

2. Make main unscrollable and nest a layer of scrollable child nodes to main

<body>
  <div class="header"></div>
  <div class="main">
    <div class="scroll-container"></div>
  </div>
  <div class="footer"></div>
</body>


<style>
  .main {
    overflow: hidden;
  }
  .scroll-container {
    height: 100%;
    overflow: auto;
  }
</style>
Copy the code





The second option has the following alternatives:


1. Ensure that the Scroll node does not overlap with the fixed node

body {
  padding: 44px 0;
}


.main {
  padding: 0;
}
Copy the code

2. Set z-index to fixed

.header,
.footer {
    z-index: 8888;
}
Copy the code

See here may have a small partner think, a simple layout, but also around so many pits, isn’t there a simple way, the answer is certainly yes, that is the third implementation of the way, Flex layout. Flex is mobile compatible with iOS 7.1+, Android 4.4+, and can be downgraded to an older version of Flexbox using tools such as Autoprefixer, which is compatible with iOS 3.2 and Android 2.1. It’s also relatively simple to implement in Flex and behaves fairly consistently across browsers. The implementation is as follows:

body {
  display: flex;
  flex-direction: column;
}
.main {
  flex: 1;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
}
.header {
  height: 44px;
}
.footer {
  height: 44px;
}
Copy the code


2, Fixed and input

If you are new to mobile Web development, you may have heard that you should not use fixed positioning on pages with input tags, because the two will always have strange problems together.


On iOS, when clicking on the input TAB to get focus and trigger the soft keyboard, the fixed position will temporarily become invalid, or it can be read as an absolute position. In a page containing scrolling, the fixed position node will scroll with other nodes.


In fact, this problem is also very easy to solve, as long as the parent node of the fixed location can not be rolled, so even if the fixed location fails, it will not be rolled with other rolling nodes, affecting the interface.


However, there are many other bugs that are difficult to solve. For example, the Android soft keyboard is invoked to block the input tag so that the user can’t see the string he or she has entered. IOS requires at least one character to scroll the corresponding input tag to the appropriate position. So to avoid these difficult potholes, try to replace fixed with Absolute or Flex on pages with form input.


Compositionstart and ComPOSItionEnd events for input

In Web development, it is common to restrict the input of form elements, such as not allowing special characters and punctuation. Normally we listen for input events:

inputElement.addEventListener('input', function(event) {
  let regex = /[^1-9a-zA-Z]/g;
  event.target.value = event.target.value.replace(regex, '');
  event.returnValue = false
});
Copy the code

This code works fine on Android, but in iOS, the input event interrupts indirect input. What is indirect input? When we type Chinese characters, such as “Xi cha”, we input pinyin in the process, and each time we input a letter, the input event will be triggered. However, it is not direct input before clicking the candidate word or clicking the “Select” button.





Therefore, the input of “Xi cha” will trigger 6 input events. If the value of each input is printed out, the result is as follows:


This is obviously not what we want, we want the input event to be triggered only after direct input, which leads to the two events I’m talking about — compositionStart and compositionEnd.

The CompositionStart event is emitted when the user begins indirect input, and the CompositionEnd event is emitted when indirect input ends, after the user selects a candidate word or clicks the “Selected” button.


var inputLock = false;
function do(inputElement) {
    var regex = /[^1-9a-zA-Z]/g;
    inputElement.value = inputElement.value.replace(regex, '');
}

inputElement.addEventListener('compositionstart', function() {
  inputLock = true;
});


inputElement.addEventListener('compositionend', function(event) {
  inputLock = false;
  do(event.target);
})


inputElement.addEventListener('input', function(event) {
  if (!inputLock) {
    do(event.target);
    event.returnValue = false;
  }
});
Copy the code


Add an inputLock variable. If inputLock is set to true and does not trigger the logic in the input event until the user has completed direct input. If inputLock is set to false and the logic in the input event is triggered after the user has completed valid input. One thing to note here is that the ComPOSItionEnd event is raised after the INPUT event, so the input event handling logic is also called when the ComPOSItionEnd event is raised.


4, iOS 1px border implementation

On iOS, due to the Retina display, a 1px border will appear as two physical pixels, so it will feel thick, which is a common problem in mobile development. There are many solutions, but each has its own pros and cons.


0.5 px border


Starting with iOS 8, iOS browsers support a 0.5px border, but Android does not support it. 0.5px is considered 0px, so this method has poor compatibility.


Background gradient


CSS3 has a gradient background, you can use the gradient background to achieve a 1px border, implementation principle is set to 1px gradient background, 50% color, 50% transparent.

@mixin commonStyle() {
  background-size: 100% 1px,1px 100% ,100% 1px, 1px 100%;
  background-repeat: no-repeat;
  background-position: top, right top,  bottom, left top;
}


@mixin border($border-color) {
  @include commonStyle();
  background-image:linear-gradient(180deg, $border-color, $border-color 50%, transparent 50%),
  linear-gradient(270deg, $border-color, $border-color 50%, transparent 50%),
  linear-gradient(0deg, $border-color, $border-color 50%, transparent 50%),
  linear-gradient(90deg, $border-color, $border-color 50%, transparent 50%);
}
Copy the code


This method works, but there is no way to achieve rounded corners.


Pseudo-classes + transform


This kind of method works by implementing the border with the box-shadow or border of the pseudo-element, then reducing it to half that size with transform. Even if there is a need for rounded corners, it can be well achieved.

@mixin hairline-common($border-radius) { position: relative; z-index: 0; &:before { position: absolute; content: ''; border-radius: $border-radius; box-sizing: border-box; transform-origin: 0 0; } } @mixin hairline($direct: 'all', $border-color: #ccc, $border-radius: 0) { @include hairline-common($border-radius); &:before { transform: scale(.5); @if $direct == 'all' { top: 0; left: 0; width: 200%; height: 200%; box-shadow: 0 0 0 1px $border-color; z-index: -1; } @else if $direct == 'left' or $direct == 'right' { #{$direct}: 0; top: 0; width: 0; height: 200%; border-#{$direct}: 1px solid $border-color; } @else { #{$direct}: 0; left: 0; width: 200%; height: 0; border-#{$direct}: 1px solid $border-color; }}}Copy the code

conclusion

Above the pit are frequently encountered in the project, each corresponding solutions are given, but as a result of the author is also entering the pit mobile Web development new one, so the scheme is not necessarily the most appropriate given, do some small work, hope can provide you with a little help, shortage of place please correct me a lot, too.