Frontend Basics HTML / CSS / JS
How Javascript works
(This example is from udemy)
There are 4 pillars.
JS
browser
- Web APIs (DOM document, AJAX XMLHttpRequest, Timeout setTimeout)
- Event loop (check) -> callback queue (onClick, onLoad, onDone)
example code:
console.log('1);
setTimeout(() => {
console.log('2');
}, 2000)
console.log(3);
result:
1
3
undefined
2
The steps:
console.log('1')
goes to CALL STACK and gets run
setTimeout()
goes to CALL STACK and gets run
setTimeout()
isn’t part of Js and pops out of CALL STACK then goes to WEB API
- WEB API starts a timer with 2 seconds
console.log('3')
goest to CALL STACK and gets run
- After 2 seconds,
setTimeout()
is done and pops out of WEB API. callback()
is added into CALLBACK QUEUE
- EVENT LOOP keeps track CALLBACK QUEUE, found
callback()
then put it into CALL STACK
callback()
goest to CALL STACK and gets run
console.log('2')
goets to CALL STACK and gets run
console.log('2')
pops out of CALL STACK
callback()
pops out of CALL STACK
SEO
搜尋引擎將在子網域的網站視為完全不同的站
而將子目錄視為一個已存在的網站組成的一部份
加上 rich snippet
這是一個提供額外的資訊當在搜尋引擎列出結果時, 然候你要列出來的資訊有很多型態, 可能是你的產品, 額外想要顯示的售價等等
型態被定義在 schema.org, 你必須先確定你想顯示的資料型態為何, 再按照這個型態所定義的標籤屬性
例如 Product, 格式就要參考 schema.org/Product, code 請參考 Google 範例
如何加上? 在 body 或 header 任意地方加上 schema 規定的屬性
即可, 可以利用 google - Structured Data Testing Tool 幫忙檢查你要加上的是否被正確分析 :
<div itemscope itemtype="http://schema.org/Book">
<span itemprop="name"> Inbound Marketing and SEO: Insights from the Moz Blog</span>
<span itemprop="author">Rand Fishkin</span>
</div>
可參考 此站
覆寫網站所有字型
html * {
font-family: 'Arial Black', 'Arial', "新細明體", 'PMingLiU', 'sans-serif';
}
PMingLiU
: 新細明體的英文版名稱
sans-serif
: 無襯線字
base64 圖檔
<img src="data:image/jpeg;base64,/9j/4AAQSknK2h//2Q==" alt="" />
Preview image and get actual width and height
HTML :
<input type="file" id="add-photo" name="add_photo">
<img width="200" height="100" id="add-photo_preview"/>
JS :
$('#add-photo').on('change', function (e) {
// Create img
var tempImg = document.createElement('img');
console.log(document.querySelector('#add-photo').files[0]); // File object
// Put local image object into tempImg
tempImg.src = window.URL.createObjectURL(document.querySelector('#add-photo').files[0]);
tempImg.onload = function() {
// Render image
window.URL.revokeObjectURL(this.src);
// We can get actual width and height.
console.log(this.width);
console.log(this.height);
};
$("#add-photo_preview").attr('src', tempImg.src);
});
Get checkbox array
HTML :
<input type="checkbox" class="courses" data-course-id="1" value="1">
<input type="checkbox" class="courses" data-course-id="2" value="2">
JS :
var courses = [];
$(".courses:checked").each(function() {
courses.push($(this).data('course-id'));
});
Get radio value
HTML :
<input type="radio" name="role" id="role-student" value="student" checked>
<input type="radio" name="role" id="role-parent" value="parent">
JS :
role = $('input[name=role]:checked').val();
name 是陣列的話要加上引號
$('input[name="user[domain]"]:checked').length // 沒選擇為 0, 選擇其中一項為 1
判斷 group radio 其中一項有沒有被選取
$('input:radio[name=language_preference]').is(":checked")
只返回 true 或 false,
uncheck radio/checkbox group
radio :
$('input[name=sex]').each(function () {
$(this).prop('checked', false);
});
checkbox :
$("input[name='exercises[]']").each(function () {
$(this).prop('checked', false);
});
Select 初始完後直接觸發
$('#exercises').on('change', funciton () {
// do something
}).trigger('change');
Javascript tirgger HTML5 native validation
HTML:
<form id="test_form">
<input type="text" name="location" required/>
</form>
JS:
$('#test_form').on('click', function () {
if (test_form.checkValidity()) {
console.log('ok');
} else {
console.log('fail');
}
});
HTML:
<div id="header">Header</div>
<div id="wrapper">
<div id="left">
<div id="sidebar">Sidebar Text here!</div>
</div>
<div id="right">This is the text of the main part of the page.</div>
<div class="clear"></div>
</div>
<div id="footer">Footer</div>
CSS:
#header {
background: #c2c2c2;
height: 50px;
}
#wrapper {
position: relative;
min-height: 500px; /* Just as an example */
width: 500px;
}
#left {
position: absolute;
background: #d7d7d7;
width: 150px;
height: 100%;
}
#right {
position: relative;
width: 350px;
float: right;
}
#sidebar {
background: #0096d7;
width: 150px;
color: #fff;
}
.clear {
clear: both;
}
#footer {
background: #c2c2c2;
height: 500px; /* Just as an example */
}
JS:
$(document).ready(function () {
var length = $('#left').height() - $('#sidebar').height() + $('#left').offset().top;
$(window).scroll(function () {
var scroll = $(this).scrollTop();
var height = $('#sidebar').height() + 'px';
if (scroll < $('#left').offset().top) {
$('#sidebar').css({
'position': 'absolute',
'top': '0'
});
} else if (scroll > length) {
$('#sidebar').css({
'position': 'absolute',
'bottom': '0',
'top': 'auto'
});
} else {
$('#sidebar').css({
'position': 'fixed',
'top': '0',
'height': height
});
}
});
});
或直接用套件 Sticky-Kit, 用法很簡單
記得在 wrapper 底下做 clear: both
, 否則 sidebar 太長會穿過 wrapper
ref: FIXING A SIDEBAR WHILE SCROLLING, UNTIL BOTTOM WITH JQUERY
HTML:
<script type="text/javascript" src="js/jquery.sortable.min.js"></script>
未分班學生 :
<ul class="name_container">
</ul>
己分班學生
<ul class="name_container">
</ul>
JS:
// 學生名字標簽可拖曳
$('.name_container').sortable({
connectWith: '.name_container'
});
CS:
/* 所有名稱標簽的長寬 */
.name_container {
min-height: 100px;
width: 200px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
list-style-type: none;
padding: 0px;
margin: 0px;
}
.name_container li {
border: 1px solid #CCC;
background: #F6F6F6;
font-family: "Tahoma";
color: #1C94C4;
margin: 2px;
height: 30px;
line-height: 30px;
overflow: hidden;
white-space: nowrap;
text-align: center;
}
.name_container li.highlight {
background: #FEE25F;
}
/* 拖移時顯示的預放位置以虛線顯示 */
li.sortable-placeholder {
border: 1px dashed #CCC;
background: none;
}
HTML:
<div class="container">
<div class="item">item1</div>
<div class="item">item2</div>
<div class="item">item3</div>
<div class="item">item4</div>
</div>
CSS:
.container{
white-space: nowrap;
overflow-x: auto;
overflow-y: hidden;
}
.item {
vertical-align: top;
width: 300px;
display: inline-block;
}
audio speed control
JS :
function initAudio(){
var audio = new Audio();
audio.src = "http://downloads.bbc.co.uk/learningenglish/features/6min/151105_6min_english_plastic_bags_download.mp3";
audio.play();
var speedlist = document.getElementById("speedlist");
speedlist.addEventListener("change",changeSpeed);
function changeSpeed(event){
audio.playbackRate = event.target.value;
}
}
window.addEventListener("load", initAudio);
HTML :
<select id="speedlist">
<option value="1">change speed</option>
<option value=".5">.5</option>
<option value="1">Normal</option>
<option value="1.5">1.5</option>
<option value="2">2</option>
</select>
判斷滑鼠 mouse event which
$('#mylink').click(function(e){
if ( (e.which == 1) ) {
alert("left button");
} else if ( (e.which == 2) ) {
alert("middle button");
} else if ( (e.which == 3) ) {
alert("right button");
}
});
失效則使用 e.preventDefault()
js l10n
util.js :
(function () {
var L10N = window.L10N || {};
window.L10N = L10N;
if (!L10N.util) {
L10N.util = {};
L10N.util.getTrans = function (key, token) {
if (typeof L10N.lang[key] === 'undefined') {
return 'undefine-string!!';
}
var value = L10N.lang[key];
if (typeof token === 'object') {
for (var i in token) {
value = value.replace('{' + i + '}', token[i]);
}
}
return value;
};
L10N.util.htmlspecialchars = function (str) {
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
};
L10N.util.htmlspecialchars_decode = function (str) {
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, "\"");
};
L10N.util.nl2br = function (str) {
return str.replace(/\n/g, "<br>");
};
L10N.util.trimDot = function (value) {
return value.replace(/[.\s]+$/, '');
};
}
})();
lang/zh-TW/l10n.js :
(function () {
var L10N = window.L10N || {};
L10N.lang = L10N.lang || {};
// common
L10N.lang['admin-common-system_info'] = '系统讯息';
// system - group
L10N.lang['admin-system-group-edit_success'] = '修改成功';
})();
test/main.js :
alert(L10N.util.getTrans('admin-common-system_info'));
一些觀念
- AngularsJS 與 bootstrap 一起使用的話要把 angular 的
$
換掉
- Angular 建議用第 2 版, 聽說改善很多效能問題
- iOS 可用 react native, android 的 react native 要再等等
- ios react native = javscript + swift
- react native 是 native app 不是 web app
jQuery validate
安裝
下載頁面, download 載下來是一大包, 只需要 dist/jquery.validate.min.js
就好
或 Rails gem
gem 'jquery-validation-rails', '~> 1.13.1'
bundle install
assets/javascripts/application.js :
//= require jquery.validate
//= require jquery.validate.localization/messages_zh_TW
//# require jquery.validate.additional-methods
Rails 記得重啟才會生效
Example
$("#my-form").validate({
rules: {
'user[first]': {
required: true
}
},
// Specify the validation error messages
messages: {
'user[first]' : {
required: 'First 未填'
}
},
submitHandler: function(form) {
form.submit();
}
});
<button type="submit" class="btn btn-success">submit</button>
送出後 validate 會自動攔截 submit 行為, 通過後才會送出
validate
required : true
equalTo : "#confirmed_password"
rangelength : [2, 20]
minlength: 8
custom validation :
$.validator.addMethod(
"alphabets", function(value, element, regexpr) {
return regexpr.test(value);
},"請輸入英文字母");
alphabets : /^[A-Za-z]{2,20}$/
預設是無法 Validate Hidden field 的, 加上 ignore: ""
就可以了
$("#form1").validate({
ignore: "",
rules: {
something: {
number:true,
min:1,
required:true
}
}
});
驗證 file 方法1
$('#my-form').validate({
rules: {},
messages: {},
...
});
$('input[name^="fileupload"]').rules('add', {
required: true,
accept: "image/jpeg, image/pjpeg"
})
驗證 file 方法2
rules: {
'user[doc_file]': {
required: true
}
},
messages: {
'user[doc_file]': {
required: '請選擇檔案'
}
},
messages
不管是自訂的 validation 還是預設的, 錯誤訊息 name 都是對應的
覆寫預設的錯誤訊息樣式
$.validator.setDefaults({
errorElement: "span",
errorClass: "help-block",
highlight: function (element, errorClass, validClass) {
$(element).closest('.form-group').addClass('has-error');
},
unhighlight: function (element, errorClass, validClass) {
$(element).closest('.form-group').removeClass('has-error');
},
errorPlacement: function (error, element) {
if (element.parent('.input-group').length || element.prop('type') === 'checkbox' || element.prop('type') === 'radio') {
error.insertAfter(element.parent());
} else {
error.insertAfter(element);
}
}
});
判斷手機瀏覽器
function isMobile(){
return (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino|android|ipad|playbook|silk/i.test(navigator.userAgent||navigator.vendor||window.opera)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test((navigator.userAgent||navigator.vendor||window.opera).substr(0,4)))
}
if(isMobile()) {
alert('建議您在桌機瀏覽以獲得最佳顯示效果');
}
RWD
手機瀏覽
css 沒有直接判斷是否是手機瀏覽的函式可用, 它是判斷它的寬度, 當它寬度大於多少或小於多少, 先看範例
// This applies from 0px to 600px
body {
background: red;
}
// This applies from 992px onwards 一般來說 bootstrap 的樣式在 992 以下就會跑掉了
@media (min-width: 992px) {
body {
background: green;
}
}
有張背景圖, 如果是手機瀏覽就用比較小的那一張, 可以這樣寫 :
#banner-back-image {
background-image:image-url('application/back_original.jpg');
@media (max-width: 500px) {
background-image:image-url('application/back_mobile.jpg');
}
background-repeat: no-repeat;
background-size: cover;
background-position:center center;
padding: 50px 0; /* fix header position */
}
一般手機寬度差不多 400 左右, 所以我在這判斷小於 500 就當它是用手機瀏覽
chrome 瀏覽器上的 UI 顏色
<meta name="theme-color" content="#db5945">
斷行
.break-word {
word-wrap: break-word;
word-break: break-all;
}
另開新頁
唯一要注意的是如果開其他網站的話 chrome 會把它擋下來, 最好是自已網站的頁面
window.open('url...', '_blank');
fix anchor 定位偏移的問題
假設使用 bootstrap 時設定 body 出現的位置時會給 padding :
body {
padding-top: 50px;
}
它會造成 anchor 的位置錯誤
解決方法是加上一個修正 anchor 位置的 css, 並在 anchor 上加上這個 class
.fix-anchor-position {
padding-top: 50px;
margin-top: -50px;
}
<div id="info" class="fix-anchor-position"> ... </div>
GA
引入 GA + [In-Page Analytics] Enable enhanced link attribution in the reports
In-Page Analytics 是用來分析網頁上按鈕各被點了幾次
-
加上
ga(‘create’, ‘UA-XXXX-X’);
ga(‘require’, ’linkid’); <= 記得加上它
ga(‘send’, ‘pageview’);
-
設定
Admin -> Property Settings -> Use enhanced link attribution -> Apply
Admin -> View Settings -> Website’s URL 要選擇對的 protocal, 很重要, 如果網站是 https 這邊設定到 http In-Page 會一直出錯
-
載 In-Page Analytics 的 chrome extension, 並且在 URL bar 後面有個盾, 點一下, 讓它執行 insecure code
-
在後台 Behaviour -> In-Page Analytics 就可以看到了, 它會 iframe 你的網頁, 並且在按鈕上顯示被點擊多少次
用新版 https://www.google.com/analytics
, 不要用舊版 https://analytics.google.com/analytics
, 否則可能會跑不出來
[In-Page Analytics] GA 後台顯示錯誤
Problem loading In-Page Analytics
We've identified problems in your setup. These may cause problems loading In-Page Analytics.
Your site is configured to set X-Frame-Options: headers. In-Page Analytics can only work in Full View mode on your site.
You can try the Page Analytics Chrome Extension which has almost identical functionality to the In-Page Analytics report, but can often resolve these issues.
然候我看到我的網站有這個 Header
X-Frame-Options:SAMEORIGIN
browsers use this header to decide whether or not your site can be iframed by other sites.
這是因為 Rails 4 為了安全性預設加上這個 header
解決方案是把這個 header 刪除, 或 value 改成 ALLOWALL
, ALLOW-FROM http://example.com
在 config/application.rb
config.action_dispatch.default_headers = {
'X-Frame-Options' => 'ALLOW-FROM https://google.com',
}
如果 GA 的 Embedded mode 還是出不來看看開發者工具 console 有沒有噴錯
Failed to execute ‘postMessage’ on ‘DOMWindow’ (未解決)
開發者工具顯示的 error
Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('https://www.google.com') does not match the recipient window's origin ('https://analytics.google.com'). inpage.js:225
有 google 到解法,手動將 analytics.google.com
改變 sub-domain 為 www.google.com
就可以了,但我會一直被 redirect 回 analytics.google.com
它可以用來偵測特定的按扭被點選幾次
-
先到 GA 後台設定 Event
Category : button (自已取)
Action : click (自已取)
Label : contact_translator (自已取)
HTML :
<button type="button" onclick="ga('send', 'event', 'button', 'click', 'contact_translator');"/>
當按下後,就可以馬上在 GA 後台 realtime 那裡看到結果了
其他
讓某塊這個變暗淡
.opacity-04 { opacity: 0.4; }
算出正確字數
function newline_char_count(text) {
var newLines = text.match(/(\r\n|\n|\r)/g);
var addition = 0;
if (newLines != null) {
addition = newLines.length;
}
return addition;
}
$('#feedback').keyup(function () {
$('#feedback-count').html($(this).val().length + newline_char_count($(this).val()));
});
JavaScript 畫一個愛心 (用 chrome console)
s="";for(k=800;k--;)
x=1.25-k%40/16,y=k/320-1.25,
s+=Math.pow(x*x+y*y-1,3)<x*x*y*y*y
?"Love"[k%4]:39==k%40?"\n":" ";s