Go тілі: нөлден өз блогыңызға дейін 45-сабақ (барлығы 50)
Өз логотипіңіз, өз атыңыз, өз белгішеңіз: блог сіздікі болады
Go курсының қырық бесінші сабағы. Блог бөтен ат, бөтен белгі әрі бөтен түс тағып жүр — осы сабаққа дейін. Ат он бір үлгіден бір баптауға көшеді, логотип сіздің файлыңыз болады, палитра — тоғыз жол, ал белгіше сіздікі. Қоса контраст саналып, екі қатеміз түзетілді. SVG — 193 байт.
Не үшін керек
Блог жұмыс істейді, оқиды, іздейді, адамдарды кіргізеді — әрі әлі күнге бөтен оқу дайындамасы сияқты көрінеді. Оны біреуге көрсетер алдында өзіңіздікі қылған жөн, ал бұл — бір кештің ісі.
Мұнда төрт бөлек нәрсе бар, әрі оларды шатастырмау керек. Ат — жоғарғы жолақта, қойынды тақырыбында әрі төменгі жолақта тұратын мәтін. Логотип — аттың қасындағы сурет. Түстер — беттің фоны, оның үстіндегі мәтін, сілтемелер мен түймелер. Белгіше — браузер қойындыда әрі бетбелгіде салатын кішкентай нәрсе.
Әрқайсысы өз орнында тұрады, әрқайсысы өзінше ауысады.
Бірден тұтас
Жаңа қалта, go mod init sabaq42. Қасында — логотипі бар static/brand қалтасы:
package main
import (
"bytes"
"embed"
"flag"
"fmt"
"html/template"
"io/fs"
"net/http"
"net/http/httptest"
"os"
"strings"
)
//go:embed static
var files embed.FS
// page — үлгі көретін нәрсе. Блогтың аты бұған баптаудан келеді, белгілеуге
// жазылмайды: белгілеуге жазылған ат әр үлгіде бірден тұрады.
type page struct {
SiteName string
Title string
}
const layout = `<!doctype html>
<html lang="kk">
<head>
<title>{{ .Title }} — {{ .SiteName }}</title>
<link rel="icon" href="/static/brand/logo.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/static/brand/apple-touch-icon.png">
</head>
<body>
<header>
<a href="/"><img src="/static/brand/logo.svg" width="40" height="40" alt=""></a>
<span>{{ .SiteName }}</span>
</header>
</body>
</html>`
func main() {
name := flag.String("name", "Менің блогым", "блогтың аты")
flag.Parse()
tpl := template.Must(template.New("layout").Parse(layout))
fmt.Println("== бүкіл бетке бір ат")
tpl.Execute(os.Stdout, page{SiteName: *name, Title: "Мақалалар"})
fmt.Println()
// Сол үлгі, басқа ат: бетті түгел қайталамас үшін өзгерген жолдарды ғана
// басып шығарамыз.
fmt.Println("== басқа ат — сол үлгі")
var buf bytes.Buffer
tpl.Execute(&buf, page{SiteName: "Дала блогы", Title: "Мақалалар"})
for _, line := range strings.Split(buf.String(), "\n") {
if strings.Contains(line, "<title>") || strings.Contains(line, "<span>") {
fmt.Println(strings.TrimSpace(line))
}
}
fmt.Println()
fmt.Println("== браузер өзі сұрайтын нәрсе")
bare := httptest.NewServer(routes(false))
defer bare.Close()
fmt.Printf("%-30s %s\n", "/favicon.ico жауапсыз:", get(bare.URL+"/favicon.ico"))
srv := httptest.NewServer(routes(true))
defer srv.Close()
fmt.Printf("%-30s %s\n", "/favicon.ico жауаппен:", get(srv.URL+"/favicon.ico"))
fmt.Printf("%-30s %s\n", "/static/brand/logo.svg:", get(srv.URL+"/static/brand/logo.svg"))
fmt.Printf("%-30s %s\n", "apple-touch-icon.png:", get(srv.URL+"/static/brand/apple-touch-icon.png"))
fmt.Println()
fmt.Println("== сурет көлемі")
svg, _ := fs.ReadFile(files, "static/brand/logo.svg")
fmt.Printf("logo.svg: %d байт, әрі бұл мәтін: %s…\n", len(svg),
strings.SplitN(string(svg), ">", 2)[0][:38])
}
func routes(withIcon bool) http.Handler {
mux := http.NewServeMux()
mux.Handle("GET /static/", http.FileServerFS(files))
// Браузер /favicon.ico-ны белгілеуде ол туралы бір сөз болмаса да өзі
// сұрайды. Жауапсыз ол 404 алады — әрі бұл әр оқырманға журналға жазылады.
if withIcon {
mux.HandleFunc("GET /favicon.ico", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/static/brand/logo.svg", http.StatusMovedPermanently)
})
}
return mux
}
func get(url string) string {
resp, err := http.Get(url)
if err != nil {
return "қате: " + err.Error()
}
defer resp.Body.Close()
return fmt.Sprintf("%d %s", resp.StatusCode, resp.Header.Get("Content-Type"))
}
Шығатыны:
== бүкіл бетке бір ат
<!doctype html>
<html lang="kk">
<head>
<title>Мақалалар — Менің блогым</title>
<link rel="icon" href="/static/brand/logo.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/static/brand/apple-touch-icon.png">
</head>
<body>
<header>
<a href="/"><img src="/static/brand/logo.svg" width="40" height="40" alt=""></a>
<span>Менің блогым</span>
</header>
</body>
</html>
== басқа ат — сол үлгі
<title>Мақалалар — Дала блогы</title>
<span>Дала блогы</span>
== браузер өзі сұрайтын нәрсе
/favicon.ico жауапсыз: 404 text/plain; charset=utf-8
/favicon.ico жауаппен: 200 image/svg+xml
/static/brand/logo.svg: 200 image/svg+xml
apple-touch-icon.png: 200 image/png
== сурет көлемі
logo.svg: 193 байт, әрі бұл мәтін: <svg xmlns="http://www.w3.org/2000/svg…
Талдау
Блогтың аты — белгілеудегі мәтін емес, баптау
Үлгіде «Менің блогым» деген сөз жоқ. {{ .SiteName }} бар, ал аттың өзі жалаушамен келеді:
name := flag.String("name", "Менің блогым", "блогтың аты")
Айырмашылық шығыстың екінші блогында көрінеді: сол үлгі, басқа ат — әрі ат кездесетін екі жол да өзгерді. Мысалда олар екеу, блогта — он бір файл, ал түзету бәрібір біреу.
Ат белгілеуге жазылып тұрғанда ол әр үлгіде тұрады: жоғарғы жолақ, төменгі жолақ, әр беттің <title>-ы. Біздің блогта ондай файл он бір — бәрін түзетуге тура келеді, әрі оныншысында бір күні ұмытады. Баптау бұл қателер тобын түбегейлі жояды — конфигурация сабағындағыдай, онда дерекқор мекенжайы дәл солай көшкен еді.
Блогта ат үлгілерге кірген адаммен бір жерде түседі: render оны бет деректеріне бір рет салады, әрі бірде-бір өңдеуші бұл туралы ойламайды.
Елестетіп көріңіз. Қабырғаға бояумен жазылған жазу емес, есіктегі тақтайша. Тақтайшаны бір минутта ауыстырады, бояуды бүкіл қабырғамен қайта жағады.
Логотип: SVG — бұл мәтін
Шығыстың соңғы жолы: біздің логотип 193 байт тартады әрі <svg дегеннен басталады. Бұл әдеттегі мағынадағы сурет емес, белгілеу — HTML-дегі сол бұрышты жақшалар, тек олар шеңбер мен үшбұрышты сипаттайды.
Осыдан белгі үшін дәл SVG алынатын үш салдар шығады:
- ол бұлдырламайды: сол бір жол қойындыдағы 16 пиксельде де, телефон экранындағы 512-де де салынады;
- ол кішкентай: сол сападағы PNG-нің ондаған килобайтына қарсы екі жүз байт;
- оны мәтінмен түзетуге болады: редакторда ашып, түсін ауыстыру үшін графикалық бағдарлама керек емес.
Өз белгіңізді қою үшін өз файлыңызды static/brand/logo.svg орнына салыңыз — үлгілердегі атты өзгертудің қажеті жоқ. Белгіңіз PNG-де болса да қалыпты: қасына салып, src-ті түзетіңіз, бірақ width пен height-ты міндетті түрде қалдырыңыз, әйтпесе сурет жүктелгенде бет секіреді.
Түстер: бүкіл блогқа тоғыз жол
Белгіні ауыстырдық, ал бет әлі бөтен: сол қызыл сілтеме, сол кілегей фон. Блогтың түсі стиль файлының ең басында тұрады:
:root {
--ink: #1a1a1a; /* мәтін */
--paper: #fffdf8; /* беттің фоны */
--accent: #d32f2f; /* сілтемелер мен түймелер */
--muted: #6b625a; /* қолтаңбалар */
--faint: #7a7169; /* төменгі жолақ пен нұсқаулар */
--line: #e6e0d6; /* жоғарғы жолақтың астындағы және төменгі жолақтың үстіндегі сызықтар */
--edge: #9d8f7b; /* енгізу өрісінің жиегі */
--field: #ffffff; /* өріс немен боялған */
--mark: #ffe08a; /* іздеуде табылған сөздер */
}
Бұл — CSS айнымалылары: --ink — ат, #1a1a1a — мән, ал төмендегі кез келген ереже мәнді var(--ink) арқылы алады. Браузер мұны өзі істей алады, ешқандай жинақтауыш та, препроцессор да керек емес.
Басты түс — үшеу, әрі солардан бастайды: --paper — беттің фоны, --ink — оның үстіндегі мәтін, --accent — сілтемелер мен түймелер. Қалған алтауы — сол түстердің реңктері: қолтаңбалар, сызықтар, өріс жиегі.
31-ші қадамда мұны санап шықтым: палитраға 17 рет жүгінеді, жариялау — тоғыз, ал палитрадан тыс жазылған түс — нөл. Демек, --accent бір жерде өзгереді, әрі онымен бірге сілтемелер, түйме, түйменің жиегі мен қате мәтіні өзгереді. Айнымалысыз #d32f2f дегенді бүкіл файлдан іздеп, бір жерін таппай қалуға тура келер еді.
Түсті көзбен шамалап алуға болмайды
Әдемі түс пен оқылатын түс — екі бөлек нәрсе, әрі айырмашылығы саналады. Ереже контраст деп аталады: мәтін жарықтығының фон жарықтығына қатынасы. Кәдімгі мәтінге 4.5:1-ден кем болмауы керек, ірі мәтін мен көрінуге тиіс жиектерге — 3:1.
Бұл — осы сабақтың екінші кішкентай бағдарламасы, go mod init sabaq42b. Ол контрастты WCAG ережесі бойынша санайды әрі түс жарай ма, жоқ па деп айтады:
package main
import (
"fmt"
"math"
"strconv"
"strings"
)
// Блогтың палитрасы: беттегі әр түс алынатын тоғыз жол.
var palette = map[string]string{
"--ink": "#1a1a1a", // мәтін
"--paper": "#fffdf8", // беттің фоны
"--accent": "#d32f2f", // сілтемелер мен түймелер
"--muted": "#6b625a", // қолтаңбалар
"--faint": "#7a7169", // төменгі жолақ пен нұсқаулар
"--edge": "#9d8f7b", // өріс жиегі
}
// pair — не нәрсенің үстінде не жазылған. WCAG нормасы: кәдімгі мәтінге
// 4.5:1, ірі мәтін мен көрінуге тиіс жиектерге 3:1.
type pair struct {
what string
fg string
bg string
need float64
}
func main() {
fmt.Println("== біздің палитра")
check([]pair{
{"беттегі мәтін", palette["--ink"], palette["--paper"], 4.5},
{"сілтеме", palette["--accent"], palette["--paper"], 4.5},
{"өріс астындағы жазу", palette["--muted"], palette["--paper"], 4.5},
{"төменгі жолақ", palette["--faint"], palette["--paper"], 4.5},
{"түймедегі мәтін", palette["--paper"], palette["--accent"], 4.5},
{"енгізу өрісінің жиегі", palette["--edge"], palette["--paper"], 3},
})
fmt.Println()
fmt.Println("== осы сабаққа дейін қадамдарда не болды")
check([]pair{
{"төменгі жолақ", "#857c72", palette["--paper"], 4.5},
{"енгізу өрісінің жиегі", "#ddd5c9", palette["--paper"], 3},
})
fmt.Println()
fmt.Println("== әдемі, бірақ оқылмайды")
check([]pair{
{"ашық сұр сілтеме", "#9aa0a6", "#ffffff", 4.5},
{"ақтағы сары", "#ffd400", "#ffffff", 4.5},
{"қағаздағы көк", "#1a56db", palette["--paper"], 4.5},
})
}
func check(pairs []pair) {
for _, p := range pairs {
r := contrast(p.fg, p.bg)
verdict := "аз, керегі " + strconv.FormatFloat(p.need, 'g', -1, 64) + ":1"
if r >= p.need {
verdict = "жарайды"
}
fmt.Printf("%-24s %s / %s %5.2f:1 %s\n", p.what, p.fg, p.bg, r, verdict)
}
}
// contrast — WCAG ережесі бойынша жарықтық қатынасы: ашығырағы қосу 0,05-ті
// күңгірттеуіне қосу 0,05-ке бөлу. Бір — түстер бірдей, жиырма бір — ақтағы қара.
func contrast(a, b string) float64 {
la, lb := luminance(a), luminance(b)
if la < lb {
la, lb = lb, la
}
return (la + 0.05) / (lb + 0.05)
}
// luminance — түсте қанша жарық бар. Адам көзі жасылды қызылдан ашығырақ
// көреді, ал көкті бәрінен күңгірт көреді, сондықтан салмақтары әртүрлі.
func luminance(hex string) float64 {
h := strings.TrimPrefix(hex, "#")
r := channel(h[0:2])
g := channel(h[2:4])
b := channel(h[4:6])
return 0.2126*r + 0.7152*g + 0.0722*b
}
// channel бір құрауышты сызықтық түрге аударады: экран түсті санға тепе-тең
// көрсетпейді, әрі осы түзетусіз есеп қате шығады.
func channel(s string) float64 {
v, err := strconv.ParseUint(s, 16, 8)
if err != nil {
return 0
}
c := float64(v) / 255
if c <= 0.03928 {
return c / 12.92
}
return math.Pow((c+0.055)/1.055, 2.4)
}
Шығатыны:
== біздің палитра
беттегі мәтін #1a1a1a / #fffdf8 17.12:1 жарайды
сілтеме #d32f2f / #fffdf8 4.90:1 жарайды
өріс астындағы жазу #6b625a / #fffdf8 5.87:1 жарайды
төменгі жолақ #7a7169 / #fffdf8 4.70:1 жарайды
түймедегі мәтін #fffdf8 / #d32f2f 4.90:1 жарайды
енгізу өрісінің жиегі #9d8f7b / #fffdf8 3.11:1 жарайды
== осы сабаққа дейін қадамдарда не болды
төменгі жолақ #857c72 / #fffdf8 4.03:1 аз, керегі 4.5:1
енгізу өрісінің жиегі #ddd5c9 / #fffdf8 1.43:1 аз, керегі 3:1
== әдемі, бірақ оқылмайды
ашық сұр сілтеме #9aa0a6 / #ffffff 2.64:1 аз, керегі 4.5:1
ақтағы сары #ffd400 / #ffffff 1.43:1 аз, керегі 4.5:1
қағаздағы көк #1a56db / #fffdf8 6.08:1 жарайды
Осы шығыста үш нәрсені көрген жөн.
Біздің палитра өтеді — бірақ қормен емес: сілтеме 4.90:1, ал норма 4.5:1. Қызылды сәл ашықтау қылсаңыз — телефон экранына күн түскенде сілтемені оқу қиындайды.
Екінші блок — біздің өз қатеміз. Осы сабаққа дейін қадамдардағы төменгі жолақ #857c72 болатын — 4.03:1, ұсақ мәтін нормасынан төмен. Ал енгізу өрісінің жиегі #ddd5c9 1.43:1 беретін: өріс мүлде дерлік сызылмаған еді, әрі нашар экранда оның шеті жоғалатын. Екі түс те қадамдарда түзетілді — енді 4.70:1 мен 3.11:1. Санағанша мұны байқамадым; көзбен бәрі дұрыс көрінген.
Үшінші блок — талғам туралы. Ашық сұр сілтеме #9aa0a6 2.64:1 береді, ал ақтағы сары — 1.43:1. Дәл сол «макетте әдемі көрінетін» әрі көшеде оқылмайтын сайттар. Ал біздің қағазымыздағы көк #1a56db — 6.08:1, оны батыл алуға болады.
Блогты түсі жағынан өзіңіздікі қылу үшін үш жолды өзгертіңіз әрі бағдарламамен тексеріңіз:
--paper: #f7f7f5; /* кілегейдің орнына салқын қағаз */
--ink: #14181f;
--accent: #1a56db; /* қызылдың орнына көк */
Елестетіп көріңіз. Дуалдың әр тақтайын бөлек бояу емес, бояу банкасын ауыстыру. Барлық тақтай өзі қайта боялады — әрі бірде-бірі ескі түсімен қалмайды.
Қараңғы тема — сол нәрсенің қайталануы. Браузер адамның жүйеде қараңғы безендіруді таңдағанын хабарлайды, әрі сол айнымалыларды қайта анықтау жеткілікті:
@media (prefers-color-scheme: dark) {
:root {
--paper: #14120f;
--ink: #efe9df;
--accent: #ff6b6b;
}
}
Төмендегі бірде-бір ережені тиюдің қажеті жоқ: олар түсті палитрадан алады. Қараңғы темадағы контраст сол бағдарламамен саналады — әрі әдетте қызылды ашықтау қылуға тура келеді, әйтпесе қараның үстінде ол «отырып қалады».
Қойындыдағы белгіше: браузер оны өзі сұрайды
Шығыстың үшінші блогы — көпті таңқалдыратын өлшенген факт:
/favicon.ico жауапсыз: 404
/favicon.ico жауаппен: 200 image/svg+xml
Браузер /favicon.ico-ны белгілеуде ол туралы бір сөз болмаса да өзі сұрайды. Жауап бермесеңіз — әр оқырманға журналда 404, ал қойындыда бос шаршы.
Жауапты екі жолмен беруге болады:
<link rel="icon" href="/static/brand/logo.svg" type="image/svg+xml">
әрі /favicon.ico-дан сол файлға бағыттаумен — мысалдағыдай. Біріншісі браузерге белгіше қайда екенін айтады; екіншісі ескі әдетпен сұрағандарды ұстайды.
Бір SVG аздық етеді: apple-touch-icon
SVG белгіше ретінде барлық қазіргі браузерге түсінікті, бір жағдайдан басқа: бетті iPhone-ның негізгі экранына қосқанда. Онда шаршы PNG керек:
<link rel="apple-touch-icon" href="/static/brand/apple-touch-icon.png">
Өлшемі — 180×180, мөлдірліксіз (iOS бәрібір өз фонын қояды), шеттерінде шағын алаңмен: жүйе бұрыштарын кеседі. Мысалда ол белгінің қасында жатыр әрі 200 image/png деп жауап береді — бір файл, әрі блог бөтен телефонда атсыз көрінуін қояды.
Белгішені браузер ұзақ есте сақтайды
Бөлек білген жөн: белгішені бәрінен қатаң кэштейді. Файлды ауыстырдыңыз, бетті жаңарттыңыз — қойындыда әлі ескісі, әрі бұл қалыпты.
Емі бақсылық емес, ат: файлды жаңа атпен сақтаңыз (logo-2.svg) әрі сілтемені түзетіңіз. Статика сабағындағы стильдермен бірдей тәсіл, әрі ол «кэшті тазалаңыз» дегеннен өзгеше — әрқашан жұмыс істейді.
Сіздің атыңызды тағы не тағып жүр
Үлгілердің ішінде жүргенде, оқырман емес, машина көретінді де түзетіп кетіңіз:
<html lang="kk">— беттің тілі. Дұрысы тұрсын, әйтпесе браузер бетті басқа тілден аударуды ұсынады;<title>— қойындыда әрі іздеу нәтижесінде көрінетін нәрсе. Мысалдағы «Бет — Блогтың аты» пішімі барлық бетке бір ат қойғаннан жақсы оқылады;<meta name="description">— іздеудегі сілтеменің астындағы жол. Ол бізде әлі жоқ, әрі бұл — тексеру парағы сабағының қарызы.
Сабақ картасы
Өз сөзіңізбен айтыңыз
Қарамай, дауыстап немесе қағазға жауап беріңіз. Жауаптар — сабақтың соңында.
- Блогтың атын неге үлгілерде емес, баптауда ұстайды?
- Түсті неге керек әр ережеде емес, палитрада жазады?
- Кәдімгі мәтінге қандай контраст керек әрі оны неге көзбен анықтауға болмайды?
/favicon.ico-ға жауап бермесе не болады?- Белгі үшін неге PNG емес, SVG алады?
Тапсырма
Міндетті. Блогты өзіңіздікі қылыңыз: ат -name жалаушасынан немесе BLOG_NAME айнымалысынан келеді әрі барлық үлгіге бірден түседі, сіздің логотипіңіз static/brand/logo.svg-де жатыр, өз үш түсіңіз :root-та тұр, ал /favicon.ico үндемей емес, жауап беріп тұрады. Фондағы мәтін мен фондағы сілтеменің контрастын сабақтағы бағдарламамен тексеріңіз: 4,5:1-ден төмен болса — палитраға қайта оралыңыз.
Мұның бәрі step-30 ішінде жасалған — өзіңіз жасағаннан кейін салыстырыңыз.
Қалауыңызша.
- Өз белгіңізді редакторда сызыңыз: шеңбер, әріп, екі сызық. SVG — бұл мәтін, біздің файлдың көшірмесінен бастауға болады.
- Қараңғы тема қосыңыз:
@media (prefers-color-scheme: dark)әрі сол айнымалылар басқа мәндермен. Ондағы контрастты да тексеріңіз — қара фонда түстер өзгеше ұстанады. - 180×180
apple-touch-icon.pngқосып, блогтың телефонның негізгі экранында қалай көрінетінін тексеріңіз. - Блогтың аты
<title>-ға да, төменгі жолаққа да, RSS тақырыбына да бір жерден түсетін қылыңыз.
Блогта бұл қайда тұрады
Біздің блогта ат он бір файлда тұрған: әр беттің тақырыбында әрі төменгі жолақта. Енді ол баптаумен келеді: -name "Дала блогы" деп іске қосу қойынды тақырыбын да, төменгі жолақтағы жолды да ауыстырады, ал render атты бет деректеріне бір рет салады — бірде-бір өңдеуші бұл туралы есінде ұстамайды.
Қадамдардағы түстер де енді бір жерде: палитраға 17 рет жүгінеді, жариялау тоғыз, ал палитрадан тыс түс бірде-бірі қалмады. Қоса контраст санағыштың екі олжасы түзетілді — төменгі жолақ 4,03 → 4,70 әрі өріс жиегі 1,43 → 3,11.
30-шы қадамда белгішенің үшеуі де жауап береді:
/favicon.ico 301 -> /static/brand/logo.svg
/static/brand/logo.svg 200 image/svg+xml 1143 байт
/static/brand/apple-touch-icon.png 200 image/png 2092 байт
Блог бөтен дайындама болуын қояды. Әрі қарай оны адамдарға көрсетуге болады — келесі сабақтар дәл соны қалай істеу керегі туралы.
Қарыздар. Беттерде іздеу жүйесіне арналған сипаттама әлі жоқ. Әлеуметтік желіге арналған сурет — сілтеме жібергенде көрінетіні — де жоқ. Әрі автордың аты еш жерде шықпайды, дерекқорда ол бар болса да.
Жауаптар
Жауаптарды көрсету
- Өйткені белгілеуде ол әр үлгіде тұрады — жоғарғы жолақ, төменгі жолақ, әр қойындының тақырыбы — әрі бір күні оны бәрінде ауыстырмайды. Баптау оны бір жерде ұстайды, ал үлгілер дайынын алады.
- Өйткені бір түс бірнеше ережеде керек: сілтеме, түйме, оның жиегі, қате мәтіні. Палитрада ол бір рет жарияланады әрі бір рет өзгереді; әйтпесе оны бүкіл файлдан іздеп, бір жерін таппай қалуға тура келеді.
- 4,5:1-ден кем емес. Көзбен анықтауға болмайды, өйткені көз жарықтықты емес, түстерді салыстырады: біздің төменгі жолағымыз дұрыс көрінген еді, ал 4,03:1 берген — нормадан төмен, әрі мұны тек санағанда көрдік.
- Браузер оны бәрібір сұрайды әрі 404 алады: журналда әр оқырманға қате пайда болады, ал қойындыда белгінің орнына бос шаршы тұрады.
- Өйткені SVG — кез келген өлшемге бұлдырламай салынатын, ондаған килобайттың орнына жүздеген байт тартатын әрі кәдімгі редакторда түзетілетін мәтін.
Пікірлер (0)
Пікір қалдыру үшін кіріңіз →
Әзірге пікір жоқ. Бірінші болыңыз.