Task 4 (Final Challenge) has been released, and the deadline for the task is from June 10 to June 30.
TASK 0004 Content: https://github.com/baidu-ife/ife/tree/master/task/task0004
My work: https://github.com/DIYgod/ife-work/tree/master/task0004
Online Demo: https://www.anotherhome.net/file/ife/task0004
This task took a total of 20 days (from May 20 to June 9).
Below are some records from my process of completing TASK 0004.
Mobile Adaptation#
"Looking back 2 to 3 years, front-end engineers were worried about whether there was still room for survival in the era of mobile internet. But today, in our team, more than half of the work of front-end engineers is focused on developing mobile web or apps. Technically, mobile web or apps are not fundamentally different from desktop web, but there are many pitfalls on mobile. By learning this part, you can become a front-end developer who is proficient in both desktop and mobile."
The requirement is for the entire product to be a SPA. At first, I had no ideas at all. After two days of looking into AngularJS, I ultimately decided to implement it myself.
Referencing Gmail:
When switching to another anchor point, only the part corresponding to this anchor point is displayed, while other parts are hidden using display. However, I didn't understand exactly how it was implemented. Executing location.href = '#mn';
in the console can also automatically modify the display, indicating that it is bound to the anchor point rather than switched through a click event.
Li Shengju told me it is achieved by listening to the URL, similar to routing in MVC. I felt it would be quite difficult to implement myself... but then I thought of another method, which simply uses the target pseudo-class in CSS3, as demonstrated below:
See the Pen jPMgre by DIYgod (@DIYgod) on CodePen.
So I modified the CSS a bit, and easily achieved mobile adaptation.
Once again, Li Shengju guided me. By analyzing Zhang Xinxu's Mobilebone framework (official website), I found a better implementation, rendering the previous implementation obsolete.
The principle is as follows: switching anchor points triggers the onhashchange event, so I bound a function to the onhashchange event that records the previous and current anchor points. By judging the previous and current anchor points, corresponding actions are taken. During the switching process, certain classes from slide out in reverse are added to the subpages to achieve the sliding effect. The specific implementation can be seen in the CSS section below. After switching is complete, unnecessary subpages are hidden, and previously added classes are cleared. That's it.
JS part:
/* Sliding effect */
window.onhashchange = function () {
var newHash = location.hash;
var oldEle = $('.' + oldHash.substr(1));
var newEle = $('.' + newHash.substr(1));
if ((oldHash == '#type' && newHash == '#task') || (oldHash == '#task' && newHash == '#details')) {
oldEle.className += ' slide out';
newEle.className += ' slide in';
newEle.style.display = 'block';
oldEle.style.display = 'block';
setTimeout(function () {
newEle.style.display = 'block';
oldEle.style.display = 'none';
oldEle.className = oldEle.className.replace(/ slide out/, '');
newEle.className = newEle.className.replace(/ slide in/, '');
}, 225);
}
else if ((oldHash == '#task' && newHash == '#type') || (oldHash == '#details' && newHash == '#task')) {
newEle.className += ' slide reverse in';
oldEle.className += ' slide reverse out';
oldEle.style.display = 'block';
newEle.style.display = 'block';
setTimeout(function () {
oldEle.style.display = 'none';
newEle.style.display = 'block';
newEle.className = newEle.className.replace(/ slide reverse in/, '');
oldEle.className = oldEle.className.replace(/ slide reverse out/, '');
}, 225);
}
oldHash = newHash;
}
CSS part:
/* Sliding effect from mobilebone */
.slide.out, .slide.in {
animation-timing-function: ease-out;
animation-duration: 225ms;
}
.slide.in {
animation-name: slideinfromright;
}
.slide.out {
animation-name: slideouttoleft;
}
.slide.reverse.out {
animation-name: slideouttoright;
}
.slide.reverse.in {
animation-name: slideinfromleft;
}
/* keyframes for slidein from sides */
@-webkit-keyframes slideinfromright {
from {
-webkit-transform: translate3d(100%, 0, 0);
}
to {
-webkit-transform: translate3d(0, 0, 0);
}
}
@keyframes slideinfromright {
from {
transform: translateX(100%);
}
to {
transform: translateX(0);
}
}
@-webkit-keyframes slideinfromleft {
from {
-webkit-transform: translate3d(-100%, 0, 0);
}
to {
-webkit-transform: translate3d(0, 0, 0);
}
}
@keyframes slideinfromleft {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
/* keyframes for slideout to sides */
@-webkit-keyframes slideouttoleft {
from {
-webkit-transform: translate3d(0, 0, 0);
}
to {
-webkit-transform: translate3d(-100%, 0, 0);
}
}
@keyframes slideouttoleft {
from {
transform: translateX(0);
}
to {
transform: translateX(-100%);
}
}
@-webkit-keyframes slideouttoright {
from {
-webkit-transform: translate3d(0, 0, 0);
}
to {
-webkit-transform: translate3d(100%, 0, 0);
}
}
@keyframes slideouttoright {
from {
transform: translateX(0);
}
to {
transform: translateX(100%);
}
}
Update: This method performs poorly in Safari.
Further update: To add, this method seems to perform poorly in mobile Safari, but works normally in Safari on Mac.
CSS Processing#
"Due to the design issues of the CSS language itself, along with some browser compatibility issues, we often end up writing a lot of redundant code or writing the same style multiple times for compatibility. To address these issues, the concepts and related methods and tools of CSS preprocessing and postprocessing have emerged.
These tools and methods help us write more maintainable CSS code more efficiently."
After research, I finally decided to use the more widely used Less.
Based on the tutorial from the online course (Less - Learn and Use), I organized a mind map for Less:
The CSS part has been refactored, and I can finally reuse it, adhering to DRY (Don't Repeat Yourself).
Additionally, I used autoprefixer with Grunt to handle browser prefixes, which was incredibly satisfying.
Security#
"Security is something that is often overlooked, but once issues arise, they can be very significant, especially for those who have not experienced enterprise development or encountered pitfalls. If you wait until you work in a company and handle actual projects, security issues can easily occur."
Existing programs have vulnerabilities. For example, if the following content is entered in the task content and saved, it will execute our custom script.
<iframe src=javascript:alert('xss');height=0 width=0></iframe>
or
<img src="1" onerror=eval("\x61\x6c\x65\x72\x74\x28\x27\x78\x73\x73\x27\x29")>
So we need to implement simple XSS protection:
In most cases, we process user input, only allowing valid values and filtering out others. However, going further, we can convert tags.
We perform HTML encoding on the input content:
function htmlEncode(str) {
return str.replace(/&/g, "&amp;")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#x27;")
.replace(/\//g, "&#x2f;")
.replace(/\n/g, "<br>");
}
For example, if a user inputs:
<iframe src=javascript:alert('xss');height=0 width=0></iframe>
The final stored version will be:
<iframe src=javascript:alert(&#x27;xss&#x27;);height=0 width=0></iframe>
Later, when displayed, the browser will convert these characters into text content instead of executable code.
Additionally, SSL is included for extra security.
Performance Optimization#
"When working on small projects, such as school website projects, the daily traffic may not exceed 500, and most access is within the campus LAN; or when developing a laboratory MIS system, you may never use the system you developed. In such projects, performance optimization is often overlooked.
However, if you are working on a project with daily page views in the tens of thousands or more, where users from all over the country access the developed pages under different network conditions, performance issues cannot be ignored. In today's network conditions, if your page takes more than 3 seconds to complete the first screen rendering, it will definitely cause a significant loss of users.
There are many aspects and tasks involved in optimizing the performance of an entire website. Most of the time, it cannot be completed solely by front-end engineers, especially in companies with clearly defined roles, where collaboration between front-end, back-end, operations, DBA, and other positions is often required. Therefore, in our course, we mainly want you to understand the various aspects involved in performance optimization, while also focusing on some key technical points in the front-end field."
Modularization#
"For a complex project, especially one involving multiple collaborators, how to reasonably divide modules, how to facilitate module loading, and how to manage dependencies between modules are issues that any project team will face. Currently, there are some relatively common solutions in the industry, such as AMD. This section hopes you can learn how to reasonably plan project modules and effectively use modular tools to optimize your project code structure through learning JavaScript modularization."
After research, I decided to use RequireJS for implementation.
I changed the way JS is referenced to:
<script src="scripts/require.js" data-main="scripts/main"></script>
Then I rewrote the JS, dividing it into four modules: main, gtd, util, and selector. However, I still felt the division wasn't quite right... I can only ask my mentor during the defense.
I encountered a problem:
There was an HTML structure like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Demo</title>
<style>
div {
height: 100px;
width: 100px;
background: #eee;
margin: 10px;
}
</style>
</head>
<body>
<div onclick="myClick();"></div>
<div onclick="myClick();"></div>
<div onclick="myClick();"></div>
<script src="js/main.js"></script>
</body>
</html>
After modularizing it, it was evident that myClick would no longer be a global function, so it couldn't be called this way.
So I tried to bind the click event within the module:
define(function () {
function init() {
var myDiv = document.getElementsByTagName('div');
for (var i = 0; i < myDiv.length; i++) {
myDiv[i].addEventListener('click', myClick(myDiv[i]));
}
myDiv[0].click();
}
function myClick(ele) {
ele.innerHTML = ele.innerHTML + 'click';
}
return {
init: init
}
});
However, I mistakenly bound it incorrectly, as you might have noticed, but I didn't see it at the time. The indirect call to myClick(myDiv[i]) on line 5 made me mistakenly think it was the result of the call on line 10 (this was just a demo; the actual situation was more complex, and the results of these two calls were indeed quite similar).
The result was that the click binding function could be called within the module (as I mistakenly thought), but there was no response when clicking on the page.
Then I reasoned: the myClick function bound to the click event is not a global function, only valid within the module, and when clicking on the page, it would call the global myClick function, hence no response.
It seemed reasonable but was not the case. After posting a question on V2EX, a kind user 7anshuai pointed out the binding error.
Then changing it to this worked:
define(function () {
function init() {
var myDiv = document.getElementsByTagName('div');
for (var i = 0; i < myDiv.length; i++) {
myDiv[i].addEventListener('click', myClick);
}
myDiv[0].click();
}
function myClick() {
this.innerHTML = this.innerHTML + 'click';
}
return {
init: init
}
});
During this time, I also tried to expose the function to the global scope like this:
window.myClick = myClick;
Although it worked, it was really a bad practice, and I was lucky I didn't just settle for that...
6. Front-end Engineering
"There are currently many front-end development tools in the industry that can automate tasks during the development process, improving development efficiency and ensuring consistency during collaboration among multiple developers, thus enhancing the overall operational efficiency of the project."
After research, I ultimately decided to use a combination of Yeoman, Bower, and Grunt for engineering transformation.
Based on the tutorial from the online course (Grunt-beginner Front-end Automation Tool), I organized a mind map:
I created a webapp project using Yeoman (requires VPN), installed other necessary packages, modified the configuration files, and then I could enjoy the incredibly efficient and impressive experience brought by various automation tools!
Here, I mainly processed the code for less compilation, handled CSS prefixes, compressed HTML, CSS, and JS, and added MD5 values to file names. The files before processing are in the app folder, and the processed files are in the disk folder.
Done, waiting for the graduation defense!