wasm_yew_canvas_checkcode/
lib.rs1mod core;
2
3#[cfg(debug_assertions)]
4use ::gloo::console;
5use ::gloo::utils;
6use ::rand::{Rng, rngs::OsRng};
7use ::wasm_bindgen::{JsCast, JsValue, UnwrapThrowExt};
8use ::web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, MouseEvent};
9use ::yew::{Callback, Component, Context, html, html::Scope, Html, NodeRef, Properties};
10use core::CanvasOpts;
11
12#[derive(Debug, PartialEq, Properties)]
13pub struct Props {
14 #[prop_or(150.0)]
15 pub width: f64,
16 #[prop_or(50.0)]
17 pub height: f64,
18 #[prop_or(7.0)]
19 pub star_size: f64,
20 #[prop_or(25)]
21 pub star_count: u8,
22 #[prop_or(22.0)]
23 pub font_size: f64,
24 #[prop_or(5)]
25 pub check_code_len: u8,
26 pub on_check_code_change: Callback<CheckCode>,
27 #[prop_or((|_| {}).into())]
28 pub reversed_hook: Callback<Scope<CanvasCheckCode>>,
29}
30pub enum Message {
31 UpdateCheckCode
32}
33pub enum CheckCode {
34 Initialize(String),
35 Update(String)
36}
37pub struct CanvasCheckCode {
38 canvas_ref: NodeRef,
39 unique_id: String,
40}
41macro_rules! draw_canvas {
42 (@core $self: ident, $ctx: ident, $canvas: ident, $custom_canvas: block, $timing: ident) => {
43 let props = $ctx.props();
44 let check_code = gen_random_characters(props.check_code_len); let $canvas = $self.canvas_ref.cast::<HtmlCanvasElement>().ok_or("未能获取 canvas 元素")?;
46 $custom_canvas
47 let window = utils::window();
48 let canvas_opts = CanvasOpts::with_canvas(&window, &$canvas, props)?;
49 $canvas.set_attribute("width", &format!("{}px", canvas_opts.width)[..])?;
50 $canvas.set_attribute("height", &format!("{}px", canvas_opts.height)[..])?;
51 core::redraw(
52 $canvas.get_context("2d")?.ok_or("浏览器画布不支持 2D 渲染上下文")?.dyn_into::<CanvasRenderingContext2d>()?,
53 canvas_opts,
54 &check_code[..]
55 )?;
56 props.on_check_code_change.emit(CheckCode::$timing(check_code));
57 };
58 ($self: ident, $ctx: ident, $canvas: ident, $custom_canvas: block) => {
59 draw_canvas!(@core $self, $ctx, $canvas, $custom_canvas, Initialize);
60 };
61 ($self: ident, $ctx: ident) => {
62 draw_canvas!(@core $self, $ctx, canvas, {}, Update);
63 };
64}
65impl CanvasCheckCode {
66 fn init_canvas(&self, ctx: &Context<Self>) -> Result<(), JsValue> {
67 let canvas_data_key = format!("data-{}", &self.unique_id[..]);
68 draw_canvas!(self, ctx, canvas, {
69 canvas.set_attribute(&canvas_data_key[..], "")?;
70 canvas.class_list().add_1("wasm-yew-canvas-checkcode")?;
71 });
72 let document = utils::document();
73 let head = document.head().ok_or("未运行于浏览器环境内,没有 head DOM 结点")?;
74 let style_data_key = format!("data-wasm-yew-canvas-checkcode-{}", &self.unique_id[..]);
76 let _style = head.query_selector(&format!("style[{}]", &style_data_key[..])[..])?.ok_or("没有现成的样式dom元素").or_else(|_| {
77 let style = document.create_element("style")?;
78 head.append_child(&style)?;
79 style.set_attribute(&style_data_key[..], "")?;
80 style.set_text_content(Some(&format!(include_str!("./wasm_yew_canvas_checkcode.css"), &canvas_data_key[..])[..]));
81 Ok::<_, JsValue>(style)
82 })?;
83 Ok(())
84 }
85 fn update_canvas(&self, ctx: &Context<Self>) -> Result<(), JsValue> {
86 draw_canvas!(self, ctx);
87 Ok(())
88 }
89}
90impl Component for CanvasCheckCode {
91 type Message = Message;
92 type Properties = Props;
93 fn create(ctx: &Context<Self>) -> Self {
94 let props = ctx.props();
95 let scope = ctx.link();
96 props.reversed_hook.emit(scope.clone());
97 Self {
98 canvas_ref: NodeRef::default(),
99 unique_id: gen_random_characters(16)
100 }
101 }
102 fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
103 match msg {
104 Message::UpdateCheckCode => self.update_canvas(ctx).unwrap_throw()
105 }
106 true
107 }
108 fn view(&self, ctx: &Context<Self>) -> Html {
109 let scope = ctx.link();
110 html! {
111 <canvas ref={self.canvas_ref.clone()} onclick={
112 scope.callback(move |event: MouseEvent| {
113 event.prevent_default();
114 event.stop_propagation();
115 event.stop_immediate_propagation();
116 #[cfg(debug_assertions)]
117 console::info!("刷新验证码");
118 Message::UpdateCheckCode
119 })
120 } />
121 }
122 }
123 fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
124 if !first_render {
125 return;
126 }
127 self.init_canvas(ctx).unwrap_throw();
128 }
129}
130fn gen_random_characters(count: u8) -> String {
131 const CHARS: [char; 67] = [
132 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
133 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
134 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
135 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D',
136 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
137 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
138 'Y', 'Z', '你', '我', '他', '她', '它'
139 ];
140 let mut characters = "".to_string();
141 for _ in 0..count {
142 characters.push(CHARS[OsRng.gen_range(0..CHARS.len())]);
143 }
144 characters
145}